Compare commits
29 Commits
Author | SHA1 | Date | |
---|---|---|---|
6751060d05 | |||
ef664607b4 | |||
62e482a14b | |||
a390ebf80f | |||
9a44960be7 | |||
c846c59b9b | |||
|
902bf6326b | ||
|
bddf3bf502 | ||
|
284131da98 | ||
927c7ea3c2 | |||
0e51a79bb6 | |||
1de9911b73 | |||
b4092c6619 | |||
024868bfd7 | |||
a0bbfd6d02 | |||
2cb6843773 | |||
87e1480077 | |||
bcd7f6a551 | |||
925b3af46b | |||
ef4efa6a6b | |||
125646d89b | |||
7af7649448 | |||
827d467077 | |||
ac8a3a12c4 | |||
286785491c | |||
263ea8910d | |||
202a942eef | |||
c7bafecce3 | |||
c67fe6f330 |
19
.github/renovate.json
vendored
Normal file
19
.github/renovate.json
vendored
Normal file
@@ -0,0 +1,19 @@
|
||||
{
|
||||
"extends": [
|
||||
"config:base"
|
||||
],
|
||||
"packageRules": [
|
||||
{
|
||||
"matchUpdateTypes": ["minor", "patch", "pin", "digest"],
|
||||
"automerge": true
|
||||
},
|
||||
{
|
||||
"groupName": "all deps",
|
||||
"separateMajorMinor": true,
|
||||
"groupSlug": "all",
|
||||
"packagePatterns": [
|
||||
"*"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
4
.github/workflows/build.yml
vendored
4
.github/workflows/build.yml
vendored
@@ -9,7 +9,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: setup
|
||||
uses: actions/setup-go@v1
|
||||
uses: actions/setup-go@v2
|
||||
with:
|
||||
go-version: 1.15
|
||||
- name: cache
|
||||
@@ -49,7 +49,7 @@ jobs:
|
||||
- name: checkout
|
||||
uses: actions/checkout@v2
|
||||
- name: lint
|
||||
uses: golangci/golangci-lint-action@v1
|
||||
uses: golangci/golangci-lint-action@v2
|
||||
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.
|
||||
|
4
.github/workflows/pr.yml
vendored
4
.github/workflows/pr.yml
vendored
@@ -9,7 +9,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: setup
|
||||
uses: actions/setup-go@v1
|
||||
uses: actions/setup-go@v2
|
||||
with:
|
||||
go-version: 1.15
|
||||
- name: cache
|
||||
@@ -49,7 +49,7 @@ jobs:
|
||||
- name: checkout
|
||||
uses: actions/checkout@v2
|
||||
- name: lint
|
||||
uses: golangci/golangci-lint-action@v1
|
||||
uses: golangci/golangci-lint-action@v2
|
||||
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.
|
||||
|
@@ -6,7 +6,7 @@ import (
|
||||
"strings"
|
||||
|
||||
"github.com/unistack-org/micro/v3/metadata"
|
||||
"github.com/unistack-org/micro/v3/registry"
|
||||
"github.com/unistack-org/micro/v3/register"
|
||||
"github.com/unistack-org/micro/v3/server"
|
||||
)
|
||||
|
||||
@@ -56,7 +56,7 @@ type Service struct {
|
||||
// The endpoint for this service
|
||||
Endpoint *Endpoint
|
||||
// Versions of this service
|
||||
Services []*registry.Service
|
||||
Services []*register.Service
|
||||
}
|
||||
|
||||
func strip(s string) string {
|
||||
@@ -98,6 +98,7 @@ func Encode(e *Endpoint) map[string]string {
|
||||
set("method", strings.Join(e.Method, ","))
|
||||
set("path", strings.Join(e.Path, ","))
|
||||
set("host", strings.Join(e.Host, ","))
|
||||
set("body", e.Body)
|
||||
|
||||
return ep
|
||||
}
|
||||
@@ -118,6 +119,7 @@ func Decode(e metadata.Metadata) *Endpoint {
|
||||
ephost, _ := e.Get("host")
|
||||
ep.Host = []string{ephost}
|
||||
ep.Handler, _ = e.Get("handler")
|
||||
ep.Body, _ = e.Get("body")
|
||||
|
||||
return ep
|
||||
}
|
||||
|
@@ -11,7 +11,7 @@ import (
|
||||
|
||||
"github.com/unistack-org/micro/v3/api"
|
||||
"github.com/unistack-org/micro/v3/api/handler"
|
||||
"github.com/unistack-org/micro/v3/registry"
|
||||
"github.com/unistack-org/micro/v3/register"
|
||||
)
|
||||
|
||||
const (
|
||||
@@ -70,7 +70,7 @@ func (h *httpHandler) getService(r *http.Request) (string, error) {
|
||||
}
|
||||
|
||||
// get the nodes for this service
|
||||
nodes := make([]*registry.Node, 0, len(service.Services))
|
||||
nodes := make([]*register.Node, 0, len(service.Services))
|
||||
for _, srv := range service.Services {
|
||||
nodes = append(nodes, srv.Nodes...)
|
||||
}
|
||||
|
@@ -12,13 +12,13 @@ import (
|
||||
"github.com/unistack-org/micro/v3/api/resolver"
|
||||
"github.com/unistack-org/micro/v3/api/resolver/vpath"
|
||||
"github.com/unistack-org/micro/v3/api/router"
|
||||
regRouter "github.com/unistack-org/micro/v3/api/router/registry"
|
||||
"github.com/unistack-org/micro/v3/registry"
|
||||
"github.com/unistack-org/micro/v3/registry/memory"
|
||||
regRouter "github.com/unistack-org/micro/v3/api/router/register"
|
||||
"github.com/unistack-org/micro/v3/register"
|
||||
"github.com/unistack-org/micro/v3/register/memory"
|
||||
)
|
||||
|
||||
func testHttp(t *testing.T, path, service, ns string) {
|
||||
r := memory.NewRegistry()
|
||||
r := memory.NewRegister()
|
||||
|
||||
l, err := net.Listen("tcp", "127.0.0.1:0")
|
||||
if err != nil {
|
||||
@@ -26,9 +26,9 @@ func testHttp(t *testing.T, path, service, ns string) {
|
||||
}
|
||||
defer l.Close()
|
||||
|
||||
s := ®istry.Service{
|
||||
s := ®ister.Service{
|
||||
Name: service,
|
||||
Nodes: []*registry.Node{
|
||||
Nodes: []*register.Node{
|
||||
{
|
||||
Id: service + "-1",
|
||||
Address: l.Addr().String(),
|
||||
@@ -58,7 +58,7 @@ func testHttp(t *testing.T, path, service, ns string) {
|
||||
// initialise the handler
|
||||
rt := regRouter.NewRouter(
|
||||
router.WithHandler("http"),
|
||||
router.WithRegistry(r),
|
||||
router.WithRegister(r),
|
||||
router.WithResolver(vpath.NewResolver(
|
||||
resolver.WithServicePrefix(ns),
|
||||
)),
|
||||
|
@@ -14,7 +14,7 @@ import (
|
||||
|
||||
"github.com/unistack-org/micro/v3/api"
|
||||
"github.com/unistack-org/micro/v3/api/handler"
|
||||
"github.com/unistack-org/micro/v3/registry"
|
||||
"github.com/unistack-org/micro/v3/register"
|
||||
)
|
||||
|
||||
const (
|
||||
@@ -72,7 +72,7 @@ func (wh *webHandler) getService(r *http.Request) (string, error) {
|
||||
}
|
||||
|
||||
// get the nodes
|
||||
nodes := make([]*registry.Node, 0, len(service.Services))
|
||||
nodes := make([]*register.Node, 0, len(service.Services))
|
||||
for _, srv := range service.Services {
|
||||
nodes = append(nodes, srv.Nodes...)
|
||||
}
|
||||
|
@@ -3,7 +3,7 @@ package resolver
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/unistack-org/micro/v3/registry"
|
||||
"github.com/unistack-org/micro/v3/register"
|
||||
)
|
||||
|
||||
// Options struct
|
||||
@@ -58,7 +58,7 @@ func Domain(n string) ResolveOption {
|
||||
|
||||
// NewResolveOptions returns new initialised resolve options
|
||||
func NewResolveOptions(opts ...ResolveOption) ResolveOptions {
|
||||
options := ResolveOptions{Domain: registry.DefaultDomain}
|
||||
options := ResolveOptions{Domain: register.DefaultDomain}
|
||||
for _, o := range opts {
|
||||
o(&options)
|
||||
}
|
||||
|
@@ -6,12 +6,12 @@ import (
|
||||
"github.com/unistack-org/micro/v3/api/resolver"
|
||||
"github.com/unistack-org/micro/v3/api/resolver/vpath"
|
||||
"github.com/unistack-org/micro/v3/logger"
|
||||
"github.com/unistack-org/micro/v3/registry"
|
||||
"github.com/unistack-org/micro/v3/register"
|
||||
)
|
||||
|
||||
type Options struct {
|
||||
Handler string
|
||||
Registry registry.Registry
|
||||
Register register.Register
|
||||
Resolver resolver.Resolver
|
||||
Logger logger.Logger
|
||||
Context context.Context
|
||||
@@ -52,10 +52,10 @@ func WithHandler(h string) Option {
|
||||
}
|
||||
}
|
||||
|
||||
// WithRegistry sets the registry
|
||||
func WithRegistry(r registry.Registry) Option {
|
||||
// WithRegister sets the register
|
||||
func WithRegister(r register.Register) Option {
|
||||
return func(o *Options) {
|
||||
o.Registry = r
|
||||
o.Register = r
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -6,13 +6,17 @@ import (
|
||||
|
||||
"github.com/unistack-org/micro/v3/logger"
|
||||
"github.com/unistack-org/micro/v3/metadata"
|
||||
"github.com/unistack-org/micro/v3/meter"
|
||||
"github.com/unistack-org/micro/v3/store"
|
||||
"github.com/unistack-org/micro/v3/tracer"
|
||||
)
|
||||
|
||||
// NewOptions creates Options struct from slice of options
|
||||
func NewOptions(opts ...Option) Options {
|
||||
options := Options{
|
||||
Tracer: tracer.DefaultTracer,
|
||||
Logger: logger.DefaultLogger,
|
||||
Meter: meter.DefaultMeter,
|
||||
}
|
||||
for _, o := range opts {
|
||||
o(&options)
|
||||
@@ -21,6 +25,7 @@ func NewOptions(opts ...Option) Options {
|
||||
}
|
||||
|
||||
type Options struct {
|
||||
Name string
|
||||
// Issuer of the service's account
|
||||
Issuer string
|
||||
// ID is the services auth ID
|
||||
@@ -41,6 +46,10 @@ type Options struct {
|
||||
Addrs []string
|
||||
// Logger sets the logger
|
||||
Logger logger.Logger
|
||||
// Meter sets tht meter
|
||||
Meter meter.Meter
|
||||
// Tracer
|
||||
Tracer tracer.Tracer
|
||||
// Context to store other options
|
||||
Context context.Context
|
||||
}
|
||||
@@ -55,6 +64,13 @@ func Addrs(addrs ...string) Option {
|
||||
}
|
||||
}
|
||||
|
||||
// Name sets the name
|
||||
func Name(n string) Option {
|
||||
return func(o *Options) {
|
||||
o.Name = n
|
||||
}
|
||||
}
|
||||
|
||||
// Issuer of the services account
|
||||
func Issuer(i string) Option {
|
||||
return func(o *Options) {
|
||||
@@ -288,3 +304,17 @@ func Logger(l logger.Logger) Option {
|
||||
o.Logger = l
|
||||
}
|
||||
}
|
||||
|
||||
// Meter sets the meter
|
||||
func Meter(m meter.Meter) Option {
|
||||
return func(o *Options) {
|
||||
o.Meter = m
|
||||
}
|
||||
}
|
||||
|
||||
// Tracer sets the meter
|
||||
func Tracer(t tracer.Tracer) Option {
|
||||
return func(o *Options) {
|
||||
o.Tracer = t
|
||||
}
|
||||
}
|
||||
|
@@ -14,6 +14,7 @@ var (
|
||||
|
||||
// Broker is an interface used for asynchronous messaging.
|
||||
type Broker interface {
|
||||
Name() string
|
||||
Init(...Option) error
|
||||
Options() Options
|
||||
Address() string
|
||||
|
247
broker/memory.go
Normal file
247
broker/memory.go
Normal file
@@ -0,0 +1,247 @@
|
||||
package broker
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"math/rand"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/google/uuid"
|
||||
"github.com/unistack-org/micro/v3/logger"
|
||||
maddr "github.com/unistack-org/micro/v3/util/addr"
|
||||
mnet "github.com/unistack-org/micro/v3/util/net"
|
||||
)
|
||||
|
||||
type memoryBroker struct {
|
||||
opts Options
|
||||
|
||||
addr string
|
||||
sync.RWMutex
|
||||
connected bool
|
||||
Subscribers map[string][]*memorySubscriber
|
||||
}
|
||||
|
||||
type memoryEvent struct {
|
||||
opts Options
|
||||
topic string
|
||||
err error
|
||||
message interface{}
|
||||
}
|
||||
|
||||
type memorySubscriber struct {
|
||||
id string
|
||||
topic string
|
||||
exit chan bool
|
||||
handler Handler
|
||||
opts SubscribeOptions
|
||||
ctx context.Context
|
||||
}
|
||||
|
||||
func (m *memoryBroker) Options() Options {
|
||||
return m.opts
|
||||
}
|
||||
|
||||
func (m *memoryBroker) Address() string {
|
||||
return m.addr
|
||||
}
|
||||
|
||||
func (m *memoryBroker) Connect(ctx context.Context) error {
|
||||
m.Lock()
|
||||
defer m.Unlock()
|
||||
|
||||
if m.connected {
|
||||
return nil
|
||||
}
|
||||
|
||||
// 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
|
||||
}
|
||||
i := rand.Intn(20000)
|
||||
// set addr with port
|
||||
addr = mnet.HostPort(addr, 10000+i)
|
||||
|
||||
m.addr = addr
|
||||
m.connected = true
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *memoryBroker) Disconnect(ctx context.Context) error {
|
||||
m.Lock()
|
||||
defer m.Unlock()
|
||||
|
||||
if !m.connected {
|
||||
return nil
|
||||
}
|
||||
|
||||
m.connected = false
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *memoryBroker) Init(opts ...Option) error {
|
||||
for _, o := range opts {
|
||||
o(&m.opts)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *memoryBroker) Publish(ctx context.Context, topic string, msg *Message, opts ...PublishOption) error {
|
||||
m.RLock()
|
||||
if !m.connected {
|
||||
m.RUnlock()
|
||||
return errors.New("not connected")
|
||||
}
|
||||
|
||||
subs, ok := m.Subscribers[topic]
|
||||
m.RUnlock()
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
|
||||
var v interface{}
|
||||
if m.opts.Codec != nil {
|
||||
buf, err := m.opts.Codec.Marshal(msg)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
v = buf
|
||||
} else {
|
||||
v = msg
|
||||
}
|
||||
|
||||
p := &memoryEvent{
|
||||
topic: topic,
|
||||
message: v,
|
||||
opts: m.opts,
|
||||
}
|
||||
|
||||
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())
|
||||
}
|
||||
}
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
return 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, errors.New("not connected")
|
||||
}
|
||||
m.RUnlock()
|
||||
|
||||
options := NewSubscribeOptions(opts...)
|
||||
|
||||
id, err := uuid.NewRandom()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
sub := &memorySubscriber{
|
||||
exit: make(chan bool, 1),
|
||||
id: id.String(),
|
||||
topic: topic,
|
||||
handler: handler,
|
||||
opts: options,
|
||||
ctx: ctx,
|
||||
}
|
||||
|
||||
m.Lock()
|
||||
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] {
|
||||
if sb.id == sub.id {
|
||||
continue
|
||||
}
|
||||
newSubscribers = append(newSubscribers, sb)
|
||||
}
|
||||
m.Subscribers[topic] = newSubscribers
|
||||
m.Unlock()
|
||||
}()
|
||||
|
||||
return sub, nil
|
||||
}
|
||||
|
||||
func (m *memoryBroker) String() string {
|
||||
return "memory"
|
||||
}
|
||||
|
||||
func (m *memoryBroker) Name() string {
|
||||
return m.opts.Name
|
||||
}
|
||||
|
||||
func (m *memoryEvent) Topic() string {
|
||||
return m.topic
|
||||
}
|
||||
|
||||
func (m *memoryEvent) Message() *Message {
|
||||
switch v := m.message.(type) {
|
||||
case *Message:
|
||||
return v
|
||||
case []byte:
|
||||
msg := &Message{}
|
||||
if err := m.opts.Codec.Unmarshal(v, msg); err != nil {
|
||||
if m.opts.Logger.V(logger.ErrorLevel) {
|
||||
m.opts.Logger.Error(m.opts.Context, "[memory]: failed to unmarshal: %v", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
return msg
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *memoryEvent) Ack() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *memoryEvent) Error() error {
|
||||
return m.err
|
||||
}
|
||||
|
||||
func (m *memorySubscriber) Options() SubscribeOptions {
|
||||
return m.opts
|
||||
}
|
||||
|
||||
func (m *memorySubscriber) Topic() string {
|
||||
return m.topic
|
||||
}
|
||||
|
||||
func (m *memorySubscriber) Unsubscribe(ctx context.Context) error {
|
||||
m.exit <- true
|
||||
return nil
|
||||
}
|
||||
|
||||
func NewBroker(opts ...Option) Broker {
|
||||
rand.Seed(time.Now().UnixNano())
|
||||
|
||||
return &memoryBroker{
|
||||
opts: NewOptions(opts...),
|
||||
Subscribers: make(map[string][]*memorySubscriber),
|
||||
}
|
||||
}
|
50
broker/memory_test.go
Normal file
50
broker/memory_test.go
Normal file
@@ -0,0 +1,50 @@
|
||||
package broker
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestMemoryBroker(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(p Event) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
sub, err := b.Subscribe(ctx, topic, fn)
|
||||
if err != nil {
|
||||
t.Fatalf("Unexpected error subscribing %v", err)
|
||||
}
|
||||
|
||||
for i := 0; i < count; i++ {
|
||||
message := &Message{
|
||||
Header: map[string]string{
|
||||
"foo": "bar",
|
||||
"id": fmt.Sprintf("%d", i),
|
||||
},
|
||||
Body: []byte(`hello world`),
|
||||
}
|
||||
|
||||
if err := b.Publish(ctx, topic, message); err != nil {
|
||||
t.Fatalf("Unexpected error publishing %d", i)
|
||||
}
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
}
|
@@ -1,77 +0,0 @@
|
||||
package broker
|
||||
|
||||
import "context"
|
||||
|
||||
type noopBroker struct {
|
||||
opts Options
|
||||
}
|
||||
|
||||
type noopSubscriber struct {
|
||||
topic string
|
||||
opts SubscribeOptions
|
||||
}
|
||||
|
||||
// NewBroker returns new noop broker
|
||||
func NewBroker(opts ...Option) Broker {
|
||||
return &noopBroker{opts: NewOptions(opts...)}
|
||||
}
|
||||
|
||||
// Init initialize broker
|
||||
func (n *noopBroker) Init(opts ...Option) error {
|
||||
for _, o := range opts {
|
||||
o(&n.opts)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Options returns broker Options
|
||||
func (n *noopBroker) Options() Options {
|
||||
return n.opts
|
||||
}
|
||||
|
||||
// Address returns broker address
|
||||
func (n *noopBroker) Address() string {
|
||||
return ""
|
||||
}
|
||||
|
||||
// Connect connects to broker
|
||||
func (n *noopBroker) Connect(ctx context.Context) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Disconnect disconnects from broker
|
||||
func (n *noopBroker) Disconnect(ctx context.Context) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Publish publishes message to broker
|
||||
func (n *noopBroker) Publish(ctx context.Context, topic string, m *Message, opts ...PublishOption) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Subscribe subscribes to broker topic
|
||||
func (n *noopBroker) Subscribe(ctx context.Context, topic string, h Handler, opts ...SubscribeOption) (Subscriber, error) {
|
||||
options := NewSubscribeOptions(opts...)
|
||||
return &noopSubscriber{topic: topic, opts: options}, nil
|
||||
}
|
||||
|
||||
// String return broker string representation
|
||||
func (n *noopBroker) String() string {
|
||||
return "noop"
|
||||
}
|
||||
|
||||
// Options returns subscriber options
|
||||
func (n *noopSubscriber) Options() SubscribeOptions {
|
||||
return n.opts
|
||||
}
|
||||
|
||||
// TOpic returns subscriber topic
|
||||
func (n *noopSubscriber) Topic() string {
|
||||
return n.topic
|
||||
}
|
||||
|
||||
// Unsubscribe unsbscribes from broker topic
|
||||
func (n *noopSubscriber) Unsubscribe(ctx context.Context) error {
|
||||
return nil
|
||||
}
|
@@ -6,36 +6,43 @@ import (
|
||||
|
||||
"github.com/unistack-org/micro/v3/codec"
|
||||
"github.com/unistack-org/micro/v3/logger"
|
||||
"github.com/unistack-org/micro/v3/registry"
|
||||
"github.com/unistack-org/micro/v3/meter"
|
||||
"github.com/unistack-org/micro/v3/register"
|
||||
"github.com/unistack-org/micro/v3/tracer"
|
||||
)
|
||||
|
||||
// Options struct
|
||||
type Options struct {
|
||||
Addrs []string
|
||||
Secure bool
|
||||
|
||||
// Codec
|
||||
Codec codec.Codec
|
||||
|
||||
// Logger the logger
|
||||
Logger logger.Logger
|
||||
// Handler executed when errors occur processing messages
|
||||
Name string
|
||||
// Addrs useed by broker
|
||||
Addrs []string
|
||||
// ErrorHandler executed when errors occur processing messages
|
||||
ErrorHandler Handler
|
||||
|
||||
// Codec used to marshal/unmarshal messages
|
||||
Codec codec.Codec
|
||||
// Logger the used logger
|
||||
Logger logger.Logger
|
||||
// Meter the used for metrics
|
||||
Meter meter.Meter
|
||||
// Tracer used for trace
|
||||
Tracer tracer.Tracer
|
||||
// TLSConfig for secure communication
|
||||
TLSConfig *tls.Config
|
||||
// Registry used for clustering
|
||||
Registry registry.Registry
|
||||
// Other options for implementations of the interface
|
||||
// can be stored in a context
|
||||
// Register used for clustering
|
||||
Register register.Register
|
||||
// Context is used for non default options
|
||||
Context context.Context
|
||||
}
|
||||
|
||||
// NewOptions create new Options
|
||||
func NewOptions(opts ...Option) Options {
|
||||
options := Options{
|
||||
Registry: registry.DefaultRegistry,
|
||||
Register: register.DefaultRegister,
|
||||
Logger: logger.DefaultLogger,
|
||||
Context: context.Background(),
|
||||
Meter: meter.DefaultMeter,
|
||||
Codec: codec.DefaultCodec,
|
||||
Tracer: tracer.DefaultTracer,
|
||||
}
|
||||
for _, o := range opts {
|
||||
o(&options)
|
||||
@@ -54,8 +61,7 @@ func Context(ctx context.Context) Option {
|
||||
type PublishOptions struct {
|
||||
// BodyOnly says that only body of the message must be published
|
||||
BodyOnly bool
|
||||
// Other options for implementations of the interface
|
||||
// can be stored in a context
|
||||
// Context for non default options
|
||||
Context context.Context
|
||||
}
|
||||
|
||||
@@ -77,10 +83,10 @@ type SubscribeOptions struct {
|
||||
// AutoAck ack messages if handler returns nil err
|
||||
AutoAck bool
|
||||
|
||||
// Handler executed when errors occur processing messages
|
||||
// ErrorHandler executed when errors occur processing messages
|
||||
ErrorHandler Handler
|
||||
|
||||
// Subscribers with the same group name
|
||||
// Group for subscriber, Subscribers with the same group name
|
||||
// will create a shared subscription where each
|
||||
// receives a subset of messages.
|
||||
Group string
|
||||
@@ -88,8 +94,7 @@ type SubscribeOptions struct {
|
||||
// BodyOnly says that consumed only body of the message
|
||||
BodyOnly bool
|
||||
|
||||
// Other options for implementations of the interface
|
||||
// can be stored in a context
|
||||
// Context is used for non default options
|
||||
Context context.Context
|
||||
}
|
||||
|
||||
@@ -198,17 +203,10 @@ func SubscribeGroup(name string) SubscribeOption {
|
||||
}
|
||||
}
|
||||
|
||||
// Registry sets registry option
|
||||
func Registry(r registry.Registry) Option {
|
||||
// Register sets register option
|
||||
func Register(r register.Register) Option {
|
||||
return func(o *Options) {
|
||||
o.Registry = r
|
||||
}
|
||||
}
|
||||
|
||||
// Secure communication with the broker
|
||||
func Secure(b bool) Option {
|
||||
return func(o *Options) {
|
||||
o.Secure = b
|
||||
o.Register = r
|
||||
}
|
||||
}
|
||||
|
||||
@@ -226,6 +224,27 @@ func Logger(l logger.Logger) Option {
|
||||
}
|
||||
}
|
||||
|
||||
// Tracer to be used for tracing
|
||||
func Tracer(t tracer.Tracer) Option {
|
||||
return func(o *Options) {
|
||||
o.Tracer = t
|
||||
}
|
||||
}
|
||||
|
||||
// Meter sets the meter
|
||||
func Meter(m meter.Meter) Option {
|
||||
return func(o *Options) {
|
||||
o.Meter = m
|
||||
}
|
||||
}
|
||||
|
||||
// Name sets the name
|
||||
func Name(n string) Option {
|
||||
return func(o *Options) {
|
||||
o.Name = n
|
||||
}
|
||||
}
|
||||
|
||||
// SubscribeContext set context
|
||||
func SubscribeContext(ctx context.Context) SubscribeOption {
|
||||
return func(o *SubscribeOptions) {
|
||||
|
@@ -18,6 +18,7 @@ var (
|
||||
// It supports Request/Response via Transport and Publishing via the Broker.
|
||||
// It also supports bidirectional streaming of requests.
|
||||
type Client interface {
|
||||
Name() string
|
||||
Init(...Option) error
|
||||
Options() Options
|
||||
NewMessage(topic string, msg interface{}, opts ...MessageOption) Message
|
||||
|
@@ -46,7 +46,20 @@ type noopRequest struct {
|
||||
|
||||
// NewClient returns new noop client
|
||||
func NewClient(opts ...Option) Client {
|
||||
return &noopClient{opts: NewOptions(opts...)}
|
||||
nc := &noopClient{opts: NewOptions(opts...)}
|
||||
// wrap in reverse
|
||||
|
||||
c := Client(nc)
|
||||
|
||||
for i := len(nc.opts.Wrappers); i > 0; i-- {
|
||||
c = nc.opts.Wrappers[i-1](c)
|
||||
}
|
||||
|
||||
return c
|
||||
}
|
||||
|
||||
func (n *noopClient) Name() string {
|
||||
return n.opts.Name
|
||||
}
|
||||
|
||||
func (n *noopRequest) Service() string {
|
||||
@@ -183,7 +196,7 @@ func (n *noopClient) Publish(ctx context.Context, p Message, opts ...PublishOpti
|
||||
|
||||
options := NewPublishOptions(opts...)
|
||||
|
||||
md, ok := metadata.FromContext(ctx)
|
||||
md, ok := metadata.FromOutgoingContext(ctx)
|
||||
if !ok {
|
||||
md = metadata.New(0)
|
||||
}
|
||||
|
@@ -7,15 +7,18 @@ import (
|
||||
"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/meter"
|
||||
"github.com/unistack-org/micro/v3/network/transport"
|
||||
"github.com/unistack-org/micro/v3/registry"
|
||||
"github.com/unistack-org/micro/v3/register"
|
||||
"github.com/unistack-org/micro/v3/router"
|
||||
"github.com/unistack-org/micro/v3/selector"
|
||||
"github.com/unistack-org/micro/v3/selector/random"
|
||||
"github.com/unistack-org/micro/v3/tracer"
|
||||
)
|
||||
|
||||
// Options holds client options
|
||||
type Options struct {
|
||||
Name string
|
||||
// Used to select codec
|
||||
ContentType string
|
||||
// Proxy address to send requests via
|
||||
@@ -28,21 +31,21 @@ type Options struct {
|
||||
Selector selector.Selector
|
||||
Transport transport.Transport
|
||||
Logger logger.Logger
|
||||
Meter meter.Meter
|
||||
// Lookup used for looking up routes
|
||||
Lookup LookupFunc
|
||||
|
||||
// Connection Pool
|
||||
PoolSize int
|
||||
PoolTTL time.Duration
|
||||
|
||||
// Middleware for client
|
||||
Tracer tracer.Tracer
|
||||
// Wrapper that used client
|
||||
Wrappers []Wrapper
|
||||
|
||||
// Default Call Options
|
||||
// CallOptions that used by default
|
||||
CallOptions CallOptions
|
||||
|
||||
// Other options for implementations of the interface
|
||||
// can be stored in a context
|
||||
// Context is used for non default options
|
||||
Context context.Context
|
||||
}
|
||||
|
||||
@@ -81,12 +84,9 @@ type CallOptions struct {
|
||||
AuthToken bool
|
||||
// Network to lookup the route within
|
||||
Network string
|
||||
|
||||
// Middleware for low level call func
|
||||
CallWrappers []CallWrapper
|
||||
|
||||
// Other options for implementations of the interface
|
||||
// can be stored in a context
|
||||
// Context is uded for non default options
|
||||
Context context.Context
|
||||
}
|
||||
|
||||
@@ -142,7 +142,6 @@ func NewRequestOptions(opts ...RequestOption) RequestOptions {
|
||||
type RequestOptions struct {
|
||||
ContentType string
|
||||
Stream bool
|
||||
|
||||
// Other options for implementations of the interface
|
||||
// can be stored in a context
|
||||
Context context.Context
|
||||
@@ -167,6 +166,8 @@ func NewOptions(opts ...Option) Options {
|
||||
Selector: random.NewSelector(),
|
||||
Logger: logger.DefaultLogger,
|
||||
Broker: broker.DefaultBroker,
|
||||
Meter: meter.DefaultMeter,
|
||||
Tracer: tracer.DefaultTracer,
|
||||
}
|
||||
|
||||
for _, o := range opts {
|
||||
@@ -183,6 +184,13 @@ func Broker(b broker.Broker) Option {
|
||||
}
|
||||
}
|
||||
|
||||
// Tracer to be used for tracing
|
||||
func Tracer(t tracer.Tracer) Option {
|
||||
return func(o *Options) {
|
||||
o.Tracer = t
|
||||
}
|
||||
}
|
||||
|
||||
// Logger to be used for log mesages
|
||||
func Logger(l logger.Logger) Option {
|
||||
return func(o *Options) {
|
||||
@@ -190,6 +198,13 @@ func Logger(l logger.Logger) Option {
|
||||
}
|
||||
}
|
||||
|
||||
// Meter to be used for metrics
|
||||
func Meter(m meter.Meter) Option {
|
||||
return func(o *Options) {
|
||||
o.Meter = m
|
||||
}
|
||||
}
|
||||
|
||||
// Codec to be used to encode/decode requests for a given content type
|
||||
func Codec(contentType string, c codec.Codec) Option {
|
||||
return func(o *Options) {
|
||||
@@ -232,11 +247,11 @@ func Transport(t transport.Transport) Option {
|
||||
}
|
||||
}
|
||||
|
||||
// Registry sets the routers registry
|
||||
func Registry(r registry.Registry) Option {
|
||||
// Register sets the routers register
|
||||
func Register(r register.Register) Option {
|
||||
return func(o *Options) {
|
||||
if o.Router != nil {
|
||||
o.Router.Init(router.Registry(r))
|
||||
o.Router.Init(router.Register(r))
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -277,6 +292,13 @@ func Backoff(fn BackoffFunc) Option {
|
||||
}
|
||||
}
|
||||
|
||||
// Name sets the client name
|
||||
func Name(n string) Option {
|
||||
return func(o *Options) {
|
||||
o.Name = n
|
||||
}
|
||||
}
|
||||
|
||||
// Lookup sets the lookup function to use for resolving service names
|
||||
func Lookup(l LookupFunc) Option {
|
||||
return func(o *Options) {
|
||||
|
@@ -25,7 +25,7 @@ var (
|
||||
|
||||
var (
|
||||
// DefaultMaxMsgSize specifies how much data codec can handle
|
||||
DefaultMaxMsgSize = 1024 * 1024 * 4 // 4Mb
|
||||
DefaultMaxMsgSize int = 1024 * 1024 * 4 // 4Mb
|
||||
DefaultCodec Codec = NewCodec()
|
||||
)
|
||||
|
||||
@@ -62,21 +62,6 @@ type Message struct {
|
||||
Body []byte
|
||||
}
|
||||
|
||||
// Option func
|
||||
type Option func(*Options)
|
||||
|
||||
// Options contains codec options
|
||||
type Options struct {
|
||||
MaxMsgSize int64
|
||||
}
|
||||
|
||||
// MaxMsgSize sets the max message size
|
||||
func MaxMsgSize(n int64) Option {
|
||||
return func(o *Options) {
|
||||
o.MaxMsgSize = n
|
||||
}
|
||||
}
|
||||
|
||||
// NewMessage creates new codec message
|
||||
func NewMessage(t MessageType) *Message {
|
||||
return &Message{Type: t, Header: metadata.New(0)}
|
||||
|
@@ -1,6 +1,7 @@
|
||||
package codec
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
)
|
||||
@@ -40,7 +41,7 @@ func (c *noopCodec) ReadBody(conn io.Reader, b interface{}) error {
|
||||
case *Frame:
|
||||
v.Data = buf
|
||||
default:
|
||||
return ErrInvalidMessage
|
||||
return json.Unmarshal(buf, v)
|
||||
}
|
||||
|
||||
return nil
|
||||
@@ -64,7 +65,11 @@ func (c *noopCodec) Write(conn io.Writer, m *Message, b interface{}) error {
|
||||
case []byte:
|
||||
v = vb
|
||||
default:
|
||||
return ErrInvalidMessage
|
||||
var err error
|
||||
v, err = json.Marshal(vb)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
_, err := conn.Write(v)
|
||||
return err
|
||||
@@ -98,30 +103,34 @@ func (c *noopCodec) Marshal(v interface{}) ([]byte, error) {
|
||||
case *Message:
|
||||
return ve.Body, nil
|
||||
}
|
||||
return nil, ErrInvalidMessage
|
||||
|
||||
return json.Marshal(v)
|
||||
}
|
||||
|
||||
func (c *noopCodec) Unmarshal(d []byte, v interface{}) error {
|
||||
var err error
|
||||
if v == nil {
|
||||
return nil
|
||||
}
|
||||
switch ve := v.(type) {
|
||||
case string:
|
||||
ve = string(d)
|
||||
return nil
|
||||
case *string:
|
||||
*ve = string(d)
|
||||
return nil
|
||||
case []byte:
|
||||
ve = d
|
||||
return nil
|
||||
case *[]byte:
|
||||
*ve = d
|
||||
return nil
|
||||
case *Frame:
|
||||
ve.Data = d
|
||||
return nil
|
||||
case *Message:
|
||||
ve.Body = d
|
||||
default:
|
||||
err = ErrInvalidMessage
|
||||
return nil
|
||||
}
|
||||
|
||||
return err
|
||||
return json.Unmarshal(d, v)
|
||||
}
|
||||
|
61
codec/options.go
Normal file
61
codec/options.go
Normal file
@@ -0,0 +1,61 @@
|
||||
package codec
|
||||
|
||||
import (
|
||||
"github.com/unistack-org/micro/v3/logger"
|
||||
"github.com/unistack-org/micro/v3/meter"
|
||||
"github.com/unistack-org/micro/v3/tracer"
|
||||
)
|
||||
|
||||
// Option func
|
||||
type Option func(*Options)
|
||||
|
||||
// Options contains codec options
|
||||
type Options struct {
|
||||
MaxMsgSize int
|
||||
Meter meter.Meter
|
||||
Logger logger.Logger
|
||||
Tracer tracer.Tracer
|
||||
}
|
||||
|
||||
// MaxMsgSize sets the max message size
|
||||
func MaxMsgSize(n int) Option {
|
||||
return func(o *Options) {
|
||||
o.MaxMsgSize = n
|
||||
}
|
||||
}
|
||||
|
||||
// Logger sets the logger
|
||||
func Logger(l logger.Logger) Option {
|
||||
return func(o *Options) {
|
||||
o.Logger = l
|
||||
}
|
||||
}
|
||||
|
||||
// Tracer to be used for tracing
|
||||
func Tracer(t tracer.Tracer) Option {
|
||||
return func(o *Options) {
|
||||
o.Tracer = t
|
||||
}
|
||||
}
|
||||
|
||||
// Meter sets the meter
|
||||
func Meter(m meter.Meter) Option {
|
||||
return func(o *Options) {
|
||||
o.Meter = m
|
||||
}
|
||||
}
|
||||
|
||||
func NewOptions(opts ...Option) Options {
|
||||
options := Options{
|
||||
Logger: logger.DefaultLogger,
|
||||
Meter: meter.DefaultMeter,
|
||||
Tracer: tracer.DefaultTracer,
|
||||
MaxMsgSize: DefaultMaxMsgSize,
|
||||
}
|
||||
|
||||
for _, o := range opts {
|
||||
o(&options)
|
||||
}
|
||||
|
||||
return options
|
||||
}
|
@@ -22,6 +22,7 @@ var (
|
||||
|
||||
// Config is an interface abstraction for dynamic configuration
|
||||
type Config interface {
|
||||
Name() string
|
||||
// Init the config
|
||||
Init(opts ...Option) error
|
||||
// Options in the config
|
||||
|
@@ -251,6 +251,10 @@ func (c *defaultConfig) String() string {
|
||||
return "default"
|
||||
}
|
||||
|
||||
func (c *defaultConfig) Name() string {
|
||||
return c.opts.Name
|
||||
}
|
||||
|
||||
func NewConfig(opts ...Option) Config {
|
||||
options := NewOptions(opts...)
|
||||
if len(options.StructTag) == 0 {
|
||||
|
@@ -5,9 +5,12 @@ import (
|
||||
|
||||
"github.com/unistack-org/micro/v3/codec"
|
||||
"github.com/unistack-org/micro/v3/logger"
|
||||
"github.com/unistack-org/micro/v3/meter"
|
||||
"github.com/unistack-org/micro/v3/tracer"
|
||||
)
|
||||
|
||||
type Options struct {
|
||||
Name string
|
||||
AllowFail bool
|
||||
BeforeLoad []func(context.Context, Config) error
|
||||
AfterLoad []func(context.Context, Config) error
|
||||
@@ -15,13 +18,17 @@ type Options struct {
|
||||
AfterSave []func(context.Context, Config) error
|
||||
// Struct that holds config data
|
||||
Struct interface{}
|
||||
// struct tag name
|
||||
// StructTag name
|
||||
StructTag string
|
||||
// logger that will be used
|
||||
// Logger that will be used
|
||||
Logger logger.Logger
|
||||
// codec that used for load/save
|
||||
// Meter that will be used
|
||||
Meter meter.Meter
|
||||
// Tracer used for trace
|
||||
Tracer tracer.Tracer
|
||||
// Codec that used for load/save
|
||||
Codec codec.Codec
|
||||
// for alternative data
|
||||
// Context for alternative data
|
||||
Context context.Context
|
||||
}
|
||||
|
||||
@@ -30,6 +37,8 @@ type Option func(o *Options)
|
||||
func NewOptions(opts ...Option) Options {
|
||||
options := Options{
|
||||
Logger: logger.DefaultLogger,
|
||||
Meter: meter.DefaultMeter,
|
||||
Tracer: tracer.DefaultTracer,
|
||||
Context: context.Background(),
|
||||
}
|
||||
for _, o := range opts {
|
||||
@@ -88,6 +97,13 @@ func Logger(l logger.Logger) Option {
|
||||
}
|
||||
}
|
||||
|
||||
// Tracer to be used for tracing
|
||||
func Tracer(t tracer.Tracer) Option {
|
||||
return func(o *Options) {
|
||||
o.Tracer = t
|
||||
}
|
||||
}
|
||||
|
||||
// Struct used as config
|
||||
func Struct(v interface{}) Option {
|
||||
return func(o *Options) {
|
||||
@@ -101,3 +117,10 @@ func StructTag(name string) Option {
|
||||
o.StructTag = name
|
||||
}
|
||||
}
|
||||
|
||||
// Name sets the name
|
||||
func Name(n string) Option {
|
||||
return func(o *Options) {
|
||||
o.Name = n
|
||||
}
|
||||
}
|
||||
|
22
context.go
Normal file
22
context.go
Normal file
@@ -0,0 +1,22 @@
|
||||
package micro
|
||||
|
||||
import "context"
|
||||
|
||||
type serviceKey struct{}
|
||||
|
||||
// FromContext retrieves a Service from the Context.
|
||||
func FromContext(ctx context.Context) (Service, bool) {
|
||||
if ctx == nil {
|
||||
return nil, false
|
||||
}
|
||||
s, ok := ctx.Value(serviceKey{}).(Service)
|
||||
return s, ok
|
||||
}
|
||||
|
||||
// NewContext returns a new Context with the Service embedded within it.
|
||||
func NewContext(ctx context.Context, s Service) context.Context {
|
||||
if ctx == nil {
|
||||
ctx = context.Background()
|
||||
}
|
||||
return context.WithValue(ctx, serviceKey{}, s)
|
||||
}
|
11
event.go
11
event.go
@@ -6,11 +6,22 @@ import (
|
||||
"github.com/unistack-org/micro/v3/client"
|
||||
)
|
||||
|
||||
// Event is used to publish messages to a topic
|
||||
type Event interface {
|
||||
// Publish publishes a message to the event topic
|
||||
Publish(ctx context.Context, msg interface{}, opts ...client.PublishOption) error
|
||||
}
|
||||
|
||||
type event struct {
|
||||
c client.Client
|
||||
topic string
|
||||
}
|
||||
|
||||
// NewEvent creates a new event publisher
|
||||
func NewEvent(topic string, c client.Client) Event {
|
||||
return &event{c, topic}
|
||||
}
|
||||
|
||||
func (e *event) Publish(ctx context.Context, msg interface{}, opts ...client.PublishOption) error {
|
||||
return e.c.Publish(ctx, e.c.NewMessage(e.topic, msg), opts...)
|
||||
}
|
||||
|
@@ -1,49 +0,0 @@
|
||||
// Package events contains interfaces for managing events within distributed systems
|
||||
package events
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"time"
|
||||
|
||||
"github.com/unistack-org/micro/v3/metadata"
|
||||
)
|
||||
|
||||
var (
|
||||
// ErrMissingTopic is returned if a blank topic was provided to publish
|
||||
ErrMissingTopic = errors.New("Missing topic")
|
||||
// ErrEncodingMessage is returned from publish if there was an error encoding the message option
|
||||
ErrEncodingMessage = errors.New("Error encoding message")
|
||||
)
|
||||
|
||||
// Stream of events
|
||||
type Stream interface {
|
||||
Publish(ctx context.Context, topic string, msg interface{}, opts ...PublishOption) error
|
||||
Subscribe(ctx context.Context, topic string, opts ...SubscribeOption) (<-chan Event, error)
|
||||
}
|
||||
|
||||
// Store of events
|
||||
type Store interface {
|
||||
Read(ctx context.Context, opts ...ReadOption) ([]*Event, error)
|
||||
Write(ctx context.Context, event *Event, opts ...WriteOption) error
|
||||
}
|
||||
|
||||
// Event is the object returned by the broker when you subscribe to a topic
|
||||
type Event struct {
|
||||
// ID to uniquely identify the event
|
||||
ID string
|
||||
// Topic of event, e.g. "registry.service.created"
|
||||
Topic string
|
||||
// Timestamp of the event
|
||||
Timestamp time.Time
|
||||
// Metadata contains the encoded event was indexed by
|
||||
Metadata metadata.Metadata
|
||||
// Payload contains the encoded message
|
||||
Payload []byte
|
||||
}
|
||||
|
||||
// Unmarshal the events message into an object
|
||||
func (e *Event) Unmarshal(v interface{}) error {
|
||||
return json.Unmarshal(e.Payload, v)
|
||||
}
|
@@ -1,124 +0,0 @@
|
||||
package events
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/unistack-org/micro/v3/metadata"
|
||||
)
|
||||
|
||||
// PublishOptions contains all the options which can be provided when publishing an event
|
||||
type PublishOptions struct {
|
||||
// Metadata contains any keys which can be used to query the data, for example a customer id
|
||||
Metadata metadata.Metadata
|
||||
// Timestamp to set for the event, if the timestamp is a zero value, the current time will be used
|
||||
Timestamp time.Time
|
||||
}
|
||||
|
||||
// PublishOption sets attributes on PublishOptions
|
||||
type PublishOption func(o *PublishOptions)
|
||||
|
||||
// WithMetadata sets the Metadata field on PublishOptions
|
||||
func WithMetadata(md metadata.Metadata) PublishOption {
|
||||
return func(o *PublishOptions) {
|
||||
o.Metadata = metadata.Copy(md)
|
||||
}
|
||||
}
|
||||
|
||||
// WithTimestamp sets the timestamp field on PublishOptions
|
||||
func WithTimestamp(t time.Time) PublishOption {
|
||||
return func(o *PublishOptions) {
|
||||
o.Timestamp = t
|
||||
}
|
||||
}
|
||||
|
||||
// SubscribeOptions contains all the options which can be provided when subscribing to a topic
|
||||
type SubscribeOptions struct {
|
||||
// Queue is the name of the subscribers queue, if two subscribers have the same queue the message
|
||||
// should only be published to one of them
|
||||
Queue string
|
||||
// StartAtTime is the time from which the messages should be consumed from. If not provided then
|
||||
// the messages will be consumed starting from the moment the Subscription starts.
|
||||
StartAtTime time.Time
|
||||
}
|
||||
|
||||
// SubscribeOption sets attributes on SubscribeOptions
|
||||
type SubscribeOption func(o *SubscribeOptions)
|
||||
|
||||
// WithQueue sets the Queue fielf on SubscribeOptions to the value provided
|
||||
func WithQueue(q string) SubscribeOption {
|
||||
return func(o *SubscribeOptions) {
|
||||
o.Queue = q
|
||||
}
|
||||
}
|
||||
|
||||
// WithStartAtTime sets the StartAtTime field on SubscribeOptions to the value provided
|
||||
func WithStartAtTime(t time.Time) SubscribeOption {
|
||||
return func(o *SubscribeOptions) {
|
||||
o.StartAtTime = t
|
||||
}
|
||||
}
|
||||
|
||||
// WriteOptions contains all the options which can be provided when writing an event to a store
|
||||
type WriteOptions struct {
|
||||
// TTL is the duration the event should be recorded for, a zero value TTL indicates the event should
|
||||
// be stored indefinitely
|
||||
TTL time.Duration
|
||||
}
|
||||
|
||||
// WriteOption sets attributes on WriteOptions
|
||||
type WriteOption func(o *WriteOptions)
|
||||
|
||||
// WithTTL sets the TTL attribute on WriteOptions
|
||||
func WithTTL(d time.Duration) WriteOption {
|
||||
return func(o *WriteOptions) {
|
||||
o.TTL = d
|
||||
}
|
||||
}
|
||||
|
||||
// ReadOptions contains all the options which can be provided when reading events from a store
|
||||
type ReadOptions struct {
|
||||
// Topic to read events from, if no topic is provided events from all topics will be returned
|
||||
Topic string
|
||||
// Query to filter the results using. The store will query the metadata provided when the event
|
||||
// was written to the store
|
||||
Query map[string]string
|
||||
// Limit the number of results to return
|
||||
Limit int
|
||||
// Offset the results by this number, useful for paginated queries
|
||||
Offset int
|
||||
}
|
||||
|
||||
// ReadOption sets attributes on ReadOptions
|
||||
type ReadOption func(o *ReadOptions)
|
||||
|
||||
// ReadTopic sets the topic attribute on ReadOptions
|
||||
func ReadTopic(t string) ReadOption {
|
||||
return func(o *ReadOptions) {
|
||||
o.Topic = t
|
||||
}
|
||||
}
|
||||
|
||||
// ReadFilter sets a key and value in the query
|
||||
func ReadFilter(key, value string) ReadOption {
|
||||
return func(o *ReadOptions) {
|
||||
if o.Query == nil {
|
||||
o.Query = map[string]string{key: value}
|
||||
} else {
|
||||
o.Query[key] = value
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ReadLimit sets the limit attribute on ReadOptions
|
||||
func ReadLimit(l int) ReadOption {
|
||||
return func(o *ReadOptions) {
|
||||
o.Limit = 1
|
||||
}
|
||||
}
|
||||
|
||||
// ReadOffset sets the offset attribute on ReadOptions
|
||||
func ReadOffset(l int) ReadOption {
|
||||
return func(o *ReadOptions) {
|
||||
o.Offset = 1
|
||||
}
|
||||
}
|
21
function.go
21
function.go
@@ -1,3 +1,5 @@
|
||||
// +build ignore
|
||||
|
||||
package micro
|
||||
|
||||
import (
|
||||
@@ -7,11 +9,28 @@ import (
|
||||
"github.com/unistack-org/micro/v3/server"
|
||||
)
|
||||
|
||||
// Function is a one time executing Service
|
||||
type Function interface {
|
||||
// Inherits Service interface
|
||||
Service
|
||||
// Done signals to complete execution
|
||||
Done() error
|
||||
// Handle registers an RPC handler
|
||||
Handle(v interface{}) error
|
||||
// Subscribe registers a subscriber
|
||||
Subscribe(topic string, v interface{}) error
|
||||
}
|
||||
|
||||
type function struct {
|
||||
cancel context.CancelFunc
|
||||
Service
|
||||
}
|
||||
|
||||
// NewFunction returns a new Function for a one time executing Service
|
||||
func NewFunction(opts ...Option) Function {
|
||||
return newFunction(opts...)
|
||||
}
|
||||
|
||||
func fnHandlerWrapper(f Function) server.HandlerWrapper {
|
||||
return func(h server.HandlerFunc) server.HandlerFunc {
|
||||
return func(ctx context.Context, req server.Request, rsp interface{}) error {
|
||||
@@ -45,7 +64,7 @@ func newFunction(opts ...Option) Function {
|
||||
// make context the last thing
|
||||
fopts = append(fopts, Context(ctx))
|
||||
|
||||
service := newService(fopts...)
|
||||
service := &service{opts: NewOptions(opts...)}
|
||||
|
||||
fn := &function{
|
||||
cancel: cancel,
|
||||
|
@@ -7,18 +7,18 @@ import (
|
||||
"sync"
|
||||
"testing"
|
||||
|
||||
rmemory "github.com/unistack-org/micro-registry-memory"
|
||||
rmemory "github.com/unistack-org/micro-register-memory"
|
||||
)
|
||||
|
||||
func TestFunction(t *testing.T) {
|
||||
var wg sync.WaitGroup
|
||||
wg.Add(1)
|
||||
|
||||
r := rmemory.NewRegistry()
|
||||
r := rmemory.NewRegister()
|
||||
|
||||
// create service
|
||||
fn := NewFunction(
|
||||
Registry(r),
|
||||
Register(r),
|
||||
Name("test.function"),
|
||||
AfterStart(func() error {
|
||||
wg.Done()
|
||||
|
7
go.mod
7
go.mod
@@ -7,15 +7,16 @@ require (
|
||||
github.com/dgrijalva/jwt-go v3.2.0+incompatible
|
||||
github.com/ef-ds/deque v1.0.4
|
||||
github.com/golang/protobuf v1.4.3
|
||||
github.com/google/uuid v1.1.5
|
||||
github.com/google/uuid v1.2.0
|
||||
github.com/imdario/mergo v0.3.11
|
||||
github.com/kr/text v0.2.0 // indirect
|
||||
github.com/miekg/dns v1.1.35
|
||||
github.com/miekg/dns v1.1.38
|
||||
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e // indirect
|
||||
github.com/oxtoacart/bpool v0.0.0-20190530202638-03653db5a59c
|
||||
github.com/patrickmn/go-cache v2.1.0+incompatible
|
||||
github.com/stretchr/testify v1.7.0
|
||||
golang.org/x/crypto v0.0.0-20201221181555-eec23a3978ad // indirect
|
||||
golang.org/x/net v0.0.0-20201224014010-6772e930b67b
|
||||
golang.org/x/net v0.0.0-20210119194325-5f4716e94777
|
||||
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e // indirect
|
||||
google.golang.org/protobuf v1.25.0
|
||||
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f // indirect
|
||||
|
14
go.sum
14
go.sum
@@ -31,20 +31,22 @@ github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMyw
|
||||
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.0 h1:/QaMHBdZ26BB3SSst0Iwl10Epc+xhTquomWX0oZEB6w=
|
||||
github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/uuid v1.1.5 h1:kxhtnfFVi+rYdOALN0B3k9UT86zVJKfBimRaciULW4I=
|
||||
github.com/google/uuid v1.1.5/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
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/imdario/mergo v0.3.11 h1:3tnifQM4i+fbajXKBHXWEH+KvNHqojZ778UH75j3bGA=
|
||||
github.com/imdario/mergo v0.3.11/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA=
|
||||
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
||||
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
||||
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
||||
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
||||
github.com/miekg/dns v1.1.35 h1:oTfOaDH+mZkdcgdIjH6yBajRGtIwcwcaR+rt23ZSrJs=
|
||||
github.com/miekg/dns v1.1.35/go.mod h1:KNUDUusw/aVsxyTYZM1oqvCicbwhgbNgztCETuNZ7xM=
|
||||
github.com/miekg/dns v1.1.38 h1:MtIY+fmHUVVgv1AXzmKMWcwdCYxTRPG1EDjpqF4RCEw=
|
||||
github.com/miekg/dns v1.1.38/go.mod h1:KNUDUusw/aVsxyTYZM1oqvCicbwhgbNgztCETuNZ7xM=
|
||||
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs=
|
||||
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
|
||||
github.com/oxtoacart/bpool v0.0.0-20190530202638-03653db5a59c h1:rp5dCmg/yLR3mgFuSOe4oEnDDmGLROTvMragMUXpTQw=
|
||||
github.com/oxtoacart/bpool v0.0.0-20190530202638-03653db5a59c/go.mod h1:X07ZCGwUbLaax7L0S3Tw4hpejzu63ZrrQiUe6W0hcy0=
|
||||
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/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
|
||||
@@ -68,8 +70,8 @@ golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn
|
||||
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20190923162816-aa69164e4478/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20201224014010-6772e930b67b h1:iFwSg7t5GZmB/Q5TjiEAsdoLDrdJRC1RiF2WhuV29Qw=
|
||||
golang.org/x/net v0.0.0-20201224014010-6772e930b67b/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||
golang.org/x/net v0.0.0-20210119194325-5f4716e94777 h1:003p0dJM77cxMSyCPFphvZf/Y5/NXf5fzg6ufd1/Oew=
|
||||
golang.org/x/net v0.0.0-20210119194325-5f4716e94777/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
|
@@ -11,6 +11,7 @@ type Option func(*Options)
|
||||
|
||||
// Options holds logger options
|
||||
type Options struct {
|
||||
Name string
|
||||
// The logging level the logger should log at. default is `InfoLevel`
|
||||
Level Level
|
||||
// fields to always be logged
|
||||
@@ -72,3 +73,10 @@ func WithContext(ctx context.Context) Option {
|
||||
o.Context = ctx
|
||||
}
|
||||
}
|
||||
|
||||
// WithName sets the name
|
||||
func withName(n string) Option {
|
||||
return func(o *Options) {
|
||||
o.Name = n
|
||||
}
|
||||
}
|
||||
|
@@ -5,45 +5,104 @@ import (
|
||||
"context"
|
||||
)
|
||||
|
||||
type mdIncomingKey struct{}
|
||||
type mdOutgoingKey struct{}
|
||||
type mdKey struct{}
|
||||
|
||||
// FromIncomingContext returns metadata from incoming ctx
|
||||
// returned metadata shoud not be modified or race condition happens
|
||||
func FromIncomingContext(ctx context.Context) (Metadata, bool) {
|
||||
if ctx == nil {
|
||||
return nil, false
|
||||
}
|
||||
md, ok := ctx.Value(mdIncomingKey{}).(*rawMetadata)
|
||||
if !ok {
|
||||
return nil, false
|
||||
}
|
||||
return md.md, ok
|
||||
}
|
||||
|
||||
// FromOutgoingContext returns metadata from outgoing ctx
|
||||
// returned metadata shoud not be modified or race condition happens
|
||||
func FromOutgoingContext(ctx context.Context) (Metadata, bool) {
|
||||
if ctx == nil {
|
||||
return nil, false
|
||||
}
|
||||
md, ok := ctx.Value(mdOutgoingKey{}).(*rawMetadata)
|
||||
if !ok {
|
||||
return nil, false
|
||||
}
|
||||
return md.md, ok
|
||||
}
|
||||
|
||||
// FromContext returns metadata from the given context
|
||||
// returned metadata shoud not be modified or race condition happens
|
||||
//
|
||||
// Deprecated: use FromIncomingContext or FromOutgoingContext
|
||||
func FromContext(ctx context.Context) (Metadata, bool) {
|
||||
if ctx == nil {
|
||||
return nil, false
|
||||
}
|
||||
md, ok := ctx.Value(metadataKey{}).(Metadata)
|
||||
md, ok := ctx.Value(mdKey{}).(*rawMetadata)
|
||||
if !ok {
|
||||
return nil, ok
|
||||
return nil, false
|
||||
}
|
||||
nmd := Copy(md)
|
||||
return nmd, ok
|
||||
return md.md, ok
|
||||
}
|
||||
|
||||
// NewContext creates a new context with the given metadata
|
||||
//
|
||||
// Deprecated: use NewIncomingContext or NewOutgoingContext
|
||||
func NewContext(ctx context.Context, md Metadata) context.Context {
|
||||
if ctx == nil {
|
||||
ctx = context.Background()
|
||||
}
|
||||
return context.WithValue(ctx, metadataKey{}, Copy(md))
|
||||
ctx = context.WithValue(ctx, mdKey{}, &rawMetadata{md})
|
||||
ctx = context.WithValue(ctx, mdIncomingKey{}, &rawMetadata{})
|
||||
ctx = context.WithValue(ctx, mdOutgoingKey{}, &rawMetadata{})
|
||||
return ctx
|
||||
}
|
||||
|
||||
// MergeContext merges metadata to existing metadata, overwriting if specified
|
||||
func MergeContext(ctx context.Context, pmd Metadata, overwrite bool) context.Context {
|
||||
// SetOutgoingContext modify outgoing context with given metadata
|
||||
func SetOutgoingContext(ctx context.Context, md Metadata) bool {
|
||||
if ctx == nil {
|
||||
return false
|
||||
}
|
||||
if omd, ok := ctx.Value(mdOutgoingKey{}).(*rawMetadata); ok {
|
||||
omd.md = md
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// SetIncomingContext modify incoming context with given metadata
|
||||
func SetIncomingContext(ctx context.Context, md Metadata) bool {
|
||||
if ctx == nil {
|
||||
return false
|
||||
}
|
||||
if omd, ok := ctx.Value(mdIncomingKey{}).(*rawMetadata); ok {
|
||||
omd.md = md
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// NewIncomingContext creates a new context with incoming metadata attached
|
||||
func NewIncomingContext(ctx context.Context, md Metadata) context.Context {
|
||||
if ctx == nil {
|
||||
ctx = context.Background()
|
||||
}
|
||||
md, ok := FromContext(ctx)
|
||||
if !ok {
|
||||
return context.WithValue(ctx, metadataKey{}, Copy(pmd))
|
||||
}
|
||||
nmd := Copy(md)
|
||||
for key, val := range pmd {
|
||||
if _, ok := nmd[key]; ok && !overwrite {
|
||||
// skip
|
||||
} else if val != "" {
|
||||
nmd.Set(key, val)
|
||||
} else {
|
||||
nmd.Del(key)
|
||||
}
|
||||
}
|
||||
return context.WithValue(ctx, metadataKey{}, nmd)
|
||||
ctx = context.WithValue(ctx, mdIncomingKey{}, &rawMetadata{md})
|
||||
ctx = context.WithValue(ctx, mdOutgoingKey{}, &rawMetadata{})
|
||||
return ctx
|
||||
}
|
||||
|
||||
// NewOutgoingContext creates a new context with outcoming metadata attached
|
||||
func NewOutgoingContext(ctx context.Context, md Metadata) context.Context {
|
||||
if ctx == nil {
|
||||
ctx = context.Background()
|
||||
}
|
||||
ctx = context.WithValue(ctx, mdOutgoingKey{}, &rawMetadata{md})
|
||||
ctx = context.WithValue(ctx, mdIncomingKey{}, &rawMetadata{})
|
||||
return ctx
|
||||
}
|
||||
|
@@ -2,21 +2,27 @@
|
||||
package metadata
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net/textproto"
|
||||
"sort"
|
||||
)
|
||||
|
||||
type metadataKey struct{}
|
||||
var (
|
||||
// HeaderPrefix for all headers passed
|
||||
HeaderPrefix = "Micro-"
|
||||
)
|
||||
|
||||
// Metadata is our way of representing request headers internally.
|
||||
// They're used at the RPC level and translate back and forth
|
||||
// from Transport headers.
|
||||
type Metadata map[string]string
|
||||
|
||||
type rawMetadata struct {
|
||||
md Metadata
|
||||
}
|
||||
|
||||
var (
|
||||
// DefaultMetadataSize used when need to init new Metadata
|
||||
DefaultMetadataSize = 6
|
||||
// defaultMetadataSize used when need to init new Metadata
|
||||
defaultMetadataSize = 2
|
||||
)
|
||||
|
||||
type Iterator struct {
|
||||
@@ -67,12 +73,9 @@ func (md Metadata) Set(key, val string) {
|
||||
// Del is used to remove value from metadata
|
||||
func (md Metadata) Del(key string) {
|
||||
// fast path
|
||||
if _, ok := md[key]; ok {
|
||||
delete(md, key)
|
||||
} else {
|
||||
// slow path
|
||||
delete(md, textproto.CanonicalMIMEHeaderKey(key))
|
||||
}
|
||||
delete(md, key)
|
||||
// slow path
|
||||
delete(md, textproto.CanonicalMIMEHeaderKey(key))
|
||||
}
|
||||
|
||||
// Copy makes a copy of the metadata
|
||||
@@ -84,39 +87,25 @@ func Copy(md Metadata) Metadata {
|
||||
return nmd
|
||||
}
|
||||
|
||||
// Del deletes key from metadata
|
||||
func Del(ctx context.Context, key string) context.Context {
|
||||
md, ok := FromContext(ctx)
|
||||
if !ok {
|
||||
md = New(0)
|
||||
}
|
||||
md.Del(key)
|
||||
return context.WithValue(ctx, metadataKey{}, md)
|
||||
}
|
||||
|
||||
// Set add key with val to metadata
|
||||
func Set(ctx context.Context, key, val string) context.Context {
|
||||
md, ok := FromContext(ctx)
|
||||
if !ok {
|
||||
md = New(0)
|
||||
}
|
||||
md.Set(key, val)
|
||||
return context.WithValue(ctx, metadataKey{}, md)
|
||||
}
|
||||
|
||||
// Get returns a single value from metadata in the context
|
||||
func Get(ctx context.Context, key string) (string, bool) {
|
||||
md, ok := FromContext(ctx)
|
||||
if !ok {
|
||||
return "", ok
|
||||
}
|
||||
return md.Get(key)
|
||||
}
|
||||
|
||||
// New return new sized metadata
|
||||
func New(size int) Metadata {
|
||||
if size == 0 {
|
||||
size = DefaultMetadataSize
|
||||
size = defaultMetadataSize
|
||||
}
|
||||
return make(Metadata, size)
|
||||
}
|
||||
|
||||
// Merge merges metadata to existing metadata, overwriting if specified
|
||||
func Merge(omd Metadata, mmd Metadata, overwrite bool) Metadata {
|
||||
nmd := Copy(omd)
|
||||
for key, val := range mmd {
|
||||
if _, ok := nmd[key]; ok && !overwrite {
|
||||
// skip
|
||||
} else if val != "" {
|
||||
nmd.Set(key, val)
|
||||
} else {
|
||||
nmd.Del(key)
|
||||
}
|
||||
}
|
||||
return nmd
|
||||
}
|
||||
|
@@ -3,10 +3,45 @@ package metadata
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"reflect"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func testCtx(ctx context.Context) {
|
||||
md := New(2)
|
||||
md.Set("Key1", "Val1_new")
|
||||
md.Set("Key3", "Val3")
|
||||
SetOutgoingContext(ctx, md)
|
||||
}
|
||||
|
||||
func TestPassing(t *testing.T) {
|
||||
ctx := context.TODO()
|
||||
md1 := New(2)
|
||||
md1.Set("Key1", "Val1")
|
||||
md1.Set("Key2", "Val2")
|
||||
|
||||
ctx = NewIncomingContext(ctx, md1)
|
||||
testCtx(ctx)
|
||||
md, ok := FromOutgoingContext(ctx)
|
||||
if !ok {
|
||||
t.Fatalf("missing metadata from outgoing context")
|
||||
}
|
||||
fmt.Printf("%#+v\n", md)
|
||||
}
|
||||
|
||||
func TestMerge(t *testing.T) {
|
||||
omd := Metadata{
|
||||
"key1": "val1",
|
||||
}
|
||||
mmd := Metadata{
|
||||
"key2": "val2",
|
||||
}
|
||||
|
||||
nmd := Merge(omd, mmd, true)
|
||||
if len(nmd) != 2 {
|
||||
t.Fatalf("merge failed: %v", nmd)
|
||||
}
|
||||
}
|
||||
|
||||
func TestIterator(t *testing.T) {
|
||||
md := Metadata{
|
||||
"1Last": "last",
|
||||
@@ -18,26 +53,27 @@ 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)
|
||||
}
|
||||
}
|
||||
|
||||
func TestMedataCanonicalKey(t *testing.T) {
|
||||
ctx := Set(context.TODO(), "x-request-id", "12345")
|
||||
v, ok := Get(ctx, "x-request-id")
|
||||
md := New(1)
|
||||
md.Set("x-request-id", "12345")
|
||||
v, ok := md.Get("x-request-id")
|
||||
if !ok {
|
||||
t.Fatalf("failed to get x-request-id")
|
||||
} else if v != "12345" {
|
||||
t.Fatalf("invalid metadata value: %s != %s", "12345", v)
|
||||
}
|
||||
|
||||
v, ok = Get(ctx, "X-Request-Id")
|
||||
v, ok = md.Get("X-Request-Id")
|
||||
if !ok {
|
||||
t.Fatalf("failed to get x-request-id")
|
||||
} else if v != "12345" {
|
||||
t.Fatalf("invalid metadata value: %s != %s", "12345", v)
|
||||
}
|
||||
v, ok = Get(ctx, "X-Request-ID")
|
||||
v, ok = md.Get("X-Request-ID")
|
||||
if !ok {
|
||||
t.Fatalf("failed to get x-request-id")
|
||||
} else if v != "12345" {
|
||||
@@ -47,9 +83,11 @@ func TestMedataCanonicalKey(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestMetadataSet(t *testing.T) {
|
||||
ctx := Set(context.TODO(), "Key", "val")
|
||||
md := New(1)
|
||||
|
||||
val, ok := Get(ctx, "Key")
|
||||
md.Set("Key", "val")
|
||||
|
||||
val, ok := md.Get("Key")
|
||||
if !ok {
|
||||
t.Fatal("key Key not found")
|
||||
}
|
||||
@@ -64,15 +102,8 @@ func TestMetadataDelete(t *testing.T) {
|
||||
"Baz": "empty",
|
||||
}
|
||||
|
||||
ctx := NewContext(context.TODO(), md)
|
||||
ctx = Del(ctx, "Baz")
|
||||
|
||||
emd, ok := FromContext(ctx)
|
||||
if !ok {
|
||||
t.Fatal("key Key not found")
|
||||
}
|
||||
|
||||
_, ok = emd["Baz"]
|
||||
md.Del("Baz")
|
||||
_, ok := md.Get("Baz")
|
||||
if ok {
|
||||
t.Fatal("key Baz not deleted")
|
||||
}
|
||||
@@ -123,42 +154,3 @@ func TestMetadataContext(t *testing.T) {
|
||||
t.Errorf("Expected metadata length 1 got %d", i)
|
||||
}
|
||||
}
|
||||
|
||||
func TestMergeContext(t *testing.T) {
|
||||
type args struct {
|
||||
existing Metadata
|
||||
append Metadata
|
||||
overwrite bool
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
want Metadata
|
||||
}{
|
||||
{
|
||||
name: "matching key, overwrite false",
|
||||
args: args{
|
||||
existing: Metadata{"Foo": "bar", "Sumo": "demo"},
|
||||
append: Metadata{"Sumo": "demo2"},
|
||||
overwrite: false,
|
||||
},
|
||||
want: Metadata{"Foo": "bar", "Sumo": "demo"},
|
||||
},
|
||||
{
|
||||
name: "matching key, overwrite true",
|
||||
args: args{
|
||||
existing: Metadata{"Foo": "bar", "Sumo": "demo"},
|
||||
append: Metadata{"Sumo": "demo2"},
|
||||
overwrite: true,
|
||||
},
|
||||
want: Metadata{"Foo": "bar", "Sumo": "demo2"},
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
if got, _ := FromContext(MergeContext(NewContext(context.TODO(), tt.args.existing), tt.args.append, tt.args.overwrite)); !reflect.DeepEqual(got, tt.want) {
|
||||
t.Errorf("MergeContext() = %v, want %v", got, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
@@ -1,22 +0,0 @@
|
||||
metrics
|
||||
=======
|
||||
|
||||
The metrics package provides a simple metrics "Reporter" interface which allows the user to submit counters, gauges and timings (along with key/value tags).
|
||||
|
||||
Implementations
|
||||
---------------
|
||||
|
||||
* Prometheus (pull): will be first
|
||||
* Prometheus (push): certainly achievable
|
||||
* InfluxDB: could quite easily be done
|
||||
* Telegraf: almost identical to the InfluxDB implementation
|
||||
* Micro: Could we provide metrics over Micro's server interface?
|
||||
|
||||
|
||||
Todo
|
||||
----
|
||||
|
||||
* Include a handler middleware which uses the Reporter interface to generate per-request level metrics
|
||||
- Throughput
|
||||
- Errors
|
||||
- Duration
|
34
meter/context.go
Normal file
34
meter/context.go
Normal file
@@ -0,0 +1,34 @@
|
||||
package meter
|
||||
|
||||
import (
|
||||
"context"
|
||||
)
|
||||
|
||||
type meterKey struct{}
|
||||
|
||||
// FromContext get meter from context
|
||||
func FromContext(ctx context.Context) (Meter, bool) {
|
||||
if ctx == nil {
|
||||
return nil, false
|
||||
}
|
||||
c, ok := ctx.Value(meterKey{}).(Meter)
|
||||
return c, ok
|
||||
}
|
||||
|
||||
// NewContext put meter in context
|
||||
func NewContext(ctx context.Context, c Meter) context.Context {
|
||||
if ctx == nil {
|
||||
ctx = context.Background()
|
||||
}
|
||||
return context.WithValue(ctx, meterKey{}, c)
|
||||
}
|
||||
|
||||
// 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)
|
||||
}
|
||||
}
|
@@ -2,25 +2,38 @@
|
||||
package meter
|
||||
|
||||
import (
|
||||
"io"
|
||||
"sort"
|
||||
"time"
|
||||
|
||||
"github.com/unistack-org/micro/v3/metadata"
|
||||
)
|
||||
|
||||
var (
|
||||
DefaultReporter Meter = NewMeter()
|
||||
// DefaultMeter is the default meter
|
||||
DefaultMeter Meter = NewMeter()
|
||||
// DefaultAddress data will be made available on this host:port
|
||||
DefaultAddress = ":9090"
|
||||
// DefaultPath the meter endpoint where the Meter data will be made available
|
||||
DefaultPath = "/metrics"
|
||||
// timingObjectives is the default spread of stats we maintain for timings / histograms:
|
||||
//defaultTimingObjectives = map[float64]float64{0.0: 0, 0.5: 0.05, 0.75: 0.04, 0.90: 0.03, 0.95: 0.02, 0.98: 0.001, 1: 0}
|
||||
// default metric prefix
|
||||
DefaultMetricPrefix = "micro_"
|
||||
// default label prefix
|
||||
DefaultLabelPrefix = "micro_"
|
||||
)
|
||||
|
||||
// Meter is an interface for collecting and instrumenting metrics
|
||||
type Meter interface {
|
||||
Name() string
|
||||
Init(...Option) error
|
||||
Counter(string, metadata.Metadata) Counter
|
||||
FloatCounter(string, metadata.Metadata) FloatCounter
|
||||
Gauge(string, func() float64, metadata.Metadata) Gauge
|
||||
Set(metadata.Metadata) Meter
|
||||
Histogram(string, metadata.Metadata) Histogram
|
||||
Summary(string, metadata.Metadata) Summary
|
||||
SummaryExt(string, time.Duration, []float64, metadata.Metadata) Summary
|
||||
Counter(string, ...Option) Counter
|
||||
FloatCounter(string, ...Option) FloatCounter
|
||||
Gauge(string, func() float64, ...Option) Gauge
|
||||
Set(...Option) Meter
|
||||
Histogram(string, ...Option) Histogram
|
||||
Summary(string, ...Option) Summary
|
||||
SummaryExt(string, time.Duration, []float64, ...Option) Summary
|
||||
Write(io.Writer, bool) error
|
||||
Options() Options
|
||||
String() string
|
||||
}
|
||||
@@ -60,3 +73,55 @@ type Summary interface {
|
||||
Update(float64)
|
||||
UpdateDuration(time.Time)
|
||||
}
|
||||
|
||||
type Labels struct {
|
||||
keys []string
|
||||
vals []string
|
||||
}
|
||||
|
||||
func (ls Labels) Len() int {
|
||||
return len(ls.keys)
|
||||
}
|
||||
|
||||
func (ls Labels) Swap(i, j int) {
|
||||
ls.keys[i], ls.keys[j] = ls.keys[j], ls.keys[i]
|
||||
ls.vals[i], ls.vals[j] = ls.vals[j], ls.vals[i]
|
||||
}
|
||||
|
||||
func (ls Labels) Less(i, j int) bool {
|
||||
return ls.vals[i] < ls.vals[j]
|
||||
}
|
||||
|
||||
func (ls Labels) Sort() {
|
||||
sort.Sort(ls)
|
||||
}
|
||||
|
||||
func (ls Labels) Append(nls Labels) Labels {
|
||||
for n := range nls.keys {
|
||||
ls.keys = append(ls.keys, nls.keys[n])
|
||||
ls.vals = append(ls.vals, nls.vals[n])
|
||||
}
|
||||
return ls
|
||||
}
|
||||
|
||||
type LabelIter struct {
|
||||
labels Labels
|
||||
cnt int
|
||||
cur int
|
||||
}
|
||||
|
||||
func (ls Labels) Iter() *LabelIter {
|
||||
ls.Sort()
|
||||
return &LabelIter{labels: ls, cnt: len(ls.keys)}
|
||||
}
|
||||
|
||||
func (iter *LabelIter) Next(k, v *string) bool {
|
||||
if iter.cur+1 > iter.cnt {
|
||||
return false
|
||||
}
|
||||
|
||||
*k = iter.labels.keys[iter.cur]
|
||||
*v = iter.labels.vals[iter.cur]
|
||||
iter.cur++
|
||||
return true
|
||||
}
|
||||
|
@@ -11,4 +11,53 @@ func TestNoopMeter(t *testing.T) {
|
||||
assert.NotNil(t, meter)
|
||||
assert.Equal(t, "/noop", meter.Options().Path)
|
||||
assert.Implements(t, new(Meter), meter)
|
||||
|
||||
cnt := meter.Counter("counter", Label("server", "noop"))
|
||||
cnt.Inc()
|
||||
|
||||
}
|
||||
|
||||
func TestLabels(t *testing.T) {
|
||||
var ls Labels
|
||||
ls.keys = []string{"type", "server"}
|
||||
ls.vals = []string{"noop", "http"}
|
||||
|
||||
ls.Sort()
|
||||
|
||||
if ls.keys[0] != "server" || ls.vals[0] != "http" {
|
||||
t.Fatalf("sort error: %v", ls)
|
||||
}
|
||||
}
|
||||
|
||||
func TestLabelsAppend(t *testing.T) {
|
||||
var ls Labels
|
||||
ls.keys = []string{"type", "server"}
|
||||
ls.vals = []string{"noop", "http"}
|
||||
|
||||
var nls Labels
|
||||
nls.keys = []string{"register"}
|
||||
nls.vals = []string{"gossip"}
|
||||
ls = ls.Append(nls)
|
||||
|
||||
ls.Sort()
|
||||
|
||||
if ls.keys[0] != "register" || ls.vals[0] != "gossip" {
|
||||
t.Fatalf("append error: %v", ls)
|
||||
}
|
||||
}
|
||||
|
||||
func TestIterator(t *testing.T) {
|
||||
var ls Labels
|
||||
ls.keys = []string{"type", "server", "register"}
|
||||
ls.vals = []string{"noop", "http", "gossip"}
|
||||
|
||||
iter := ls.Iter()
|
||||
var k, v string
|
||||
cnt := 0
|
||||
for iter.Next(&k, &v) {
|
||||
if cnt == 1 && (k != "server" || v != "http") {
|
||||
t.Fatalf("iter error: %s != %s || %s != %s", k, "server", v, "http")
|
||||
}
|
||||
cnt++
|
||||
}
|
||||
}
|
||||
|
@@ -1,22 +1,23 @@
|
||||
package meter
|
||||
|
||||
import (
|
||||
"io"
|
||||
"time"
|
||||
|
||||
"github.com/unistack-org/micro/v3/metadata"
|
||||
)
|
||||
|
||||
// NoopMeter is an noop implementation of Meter
|
||||
type noopMeter struct {
|
||||
opts Options
|
||||
md metadata.Metadata
|
||||
opts Options
|
||||
labels Labels
|
||||
}
|
||||
|
||||
// NewMeter returns a configured noop reporter:
|
||||
func NewMeter(opts ...Option) Meter {
|
||||
return &noopMeter{
|
||||
opts: NewOptions(opts...),
|
||||
}
|
||||
return &noopMeter{opts: NewOptions(opts...)}
|
||||
}
|
||||
|
||||
func (r *noopMeter) Name() string {
|
||||
return r.opts.Name
|
||||
}
|
||||
|
||||
// Init initialize options
|
||||
@@ -28,38 +29,52 @@ func (r *noopMeter) Init(opts ...Option) error {
|
||||
}
|
||||
|
||||
// Counter implements the Meter interface
|
||||
func (r *noopMeter) Counter(name string, md metadata.Metadata) Counter {
|
||||
return &noopCounter{}
|
||||
func (r *noopMeter) Counter(name string, opts ...Option) Counter {
|
||||
options := Options{}
|
||||
for _, o := range opts {
|
||||
o(&options)
|
||||
}
|
||||
return &noopCounter{labels: options.Labels}
|
||||
}
|
||||
|
||||
// FloatCounter implements the Meter interface
|
||||
func (r *noopMeter) FloatCounter(name string, md metadata.Metadata) FloatCounter {
|
||||
func (r *noopMeter) FloatCounter(name string, opts ...Option) FloatCounter {
|
||||
return &noopFloatCounter{}
|
||||
}
|
||||
|
||||
// Gauge implements the Meter interface
|
||||
func (r *noopMeter) Gauge(name string, f func() float64, md metadata.Metadata) Gauge {
|
||||
func (r *noopMeter) Gauge(name string, f func() float64, opts ...Option) Gauge {
|
||||
return &noopGauge{}
|
||||
}
|
||||
|
||||
// Summary implements the Meter interface
|
||||
func (r *noopMeter) Summary(name string, md metadata.Metadata) Summary {
|
||||
func (r *noopMeter) Summary(name string, opts ...Option) Summary {
|
||||
return &noopSummary{}
|
||||
}
|
||||
|
||||
// SummaryExt implements the Meter interface
|
||||
func (r *noopMeter) SummaryExt(name string, window time.Duration, quantiles []float64, md metadata.Metadata) Summary {
|
||||
func (r *noopMeter) SummaryExt(name string, window time.Duration, quantiles []float64, opts ...Option) Summary {
|
||||
return &noopSummary{}
|
||||
}
|
||||
|
||||
// Histogram implements the Meter interface
|
||||
func (r *noopMeter) Histogram(name string, md metadata.Metadata) Histogram {
|
||||
func (r *noopMeter) Histogram(name string, opts ...Option) Histogram {
|
||||
return &noopHistogram{}
|
||||
}
|
||||
|
||||
// Set implements the Meter interface
|
||||
func (r *noopMeter) Set(md metadata.Metadata) Meter {
|
||||
return &noopMeter{opts: r.opts, md: metadata.Copy(md)}
|
||||
func (r *noopMeter) Set(opts ...Option) Meter {
|
||||
m := &noopMeter{opts: r.opts}
|
||||
|
||||
for _, o := range opts {
|
||||
o(&m.opts)
|
||||
}
|
||||
|
||||
return m
|
||||
}
|
||||
|
||||
func (r *noopMeter) Write(w io.Writer, withProcessMetrics bool) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Options implements the Meter interface
|
||||
@@ -72,7 +87,9 @@ func (r *noopMeter) String() string {
|
||||
return "noop"
|
||||
}
|
||||
|
||||
type noopCounter struct{}
|
||||
type noopCounter struct {
|
||||
labels Labels
|
||||
}
|
||||
|
||||
func (r *noopCounter) Add(int) {
|
||||
|
||||
|
@@ -4,16 +4,6 @@ import (
|
||||
"context"
|
||||
|
||||
"github.com/unistack-org/micro/v3/logger"
|
||||
"github.com/unistack-org/micro/v3/metadata"
|
||||
)
|
||||
|
||||
var (
|
||||
// The Meter data will be made available on this port
|
||||
DefaultAddress = ":9090"
|
||||
// This is the endpoint where the Meter data will be made available ("/metrics" is the default)
|
||||
DefaultPath = "/metrics"
|
||||
// timingObjectives is the default spread of stats we maintain for timings / histograms:
|
||||
//defaultTimingObjectives = map[float64]float64{0.0: 0, 0.5: 0.05, 0.75: 0.04, 0.90: 0.03, 0.95: 0.02, 0.98: 0.001, 1: 0}
|
||||
)
|
||||
|
||||
// Option powers the configuration for metrics implementations:
|
||||
@@ -21,23 +11,26 @@ type Option func(*Options)
|
||||
|
||||
// Options for metrics implementations:
|
||||
type Options struct {
|
||||
Address string
|
||||
Path string
|
||||
Metadata metadata.Metadata
|
||||
Name string
|
||||
Address string
|
||||
Path string
|
||||
Labels Labels
|
||||
//TimingObjectives map[float64]float64
|
||||
Logger logger.Logger
|
||||
Context context.Context
|
||||
Logger logger.Logger
|
||||
Context context.Context
|
||||
MetricPrefix string
|
||||
LabelPrefix string
|
||||
}
|
||||
|
||||
// NewOptions prepares a set of options:
|
||||
func NewOptions(opt ...Option) Options {
|
||||
opts := Options{
|
||||
Address: DefaultAddress,
|
||||
Metadata: metadata.New(3), // 3 elements contains service name, version and id
|
||||
Path: DefaultPath,
|
||||
// TimingObjectives: defaultTimingObjectives,
|
||||
Context: context.Background(),
|
||||
Logger: logger.DefaultLogger,
|
||||
Address: DefaultAddress,
|
||||
Path: DefaultPath,
|
||||
Context: context.Background(),
|
||||
Logger: logger.DefaultLogger,
|
||||
MetricPrefix: DefaultMetricPrefix,
|
||||
LabelPrefix: DefaultLabelPrefix,
|
||||
}
|
||||
|
||||
for _, o := range opt {
|
||||
@@ -47,7 +40,7 @@ func NewOptions(opt ...Option) Options {
|
||||
return opts
|
||||
}
|
||||
|
||||
// Cntext sets the metrics context
|
||||
// Context sets the metrics context
|
||||
func Context(ctx context.Context) Option {
|
||||
return func(o *Options) {
|
||||
o.Context = ctx
|
||||
@@ -68,12 +61,14 @@ func Address(value string) Option {
|
||||
}
|
||||
}
|
||||
|
||||
// Metadata will be added to every metric
|
||||
func Metadata(md metadata.Metadata) Option {
|
||||
/*
|
||||
// Labels be added to every metric
|
||||
func Labels(labels []string) Option {
|
||||
return func(o *Options) {
|
||||
o.Metadata = metadata.Copy(md)
|
||||
o.Labels = labels
|
||||
}
|
||||
}
|
||||
*/
|
||||
|
||||
/*
|
||||
// TimingObjectives defines the desired spread of statistics for histogram / timing metrics:
|
||||
@@ -90,3 +85,18 @@ func Logger(l logger.Logger) Option {
|
||||
o.Logger = l
|
||||
}
|
||||
}
|
||||
|
||||
// Label sets the label
|
||||
func Label(key, val string) Option {
|
||||
return func(o *Options) {
|
||||
o.Labels.keys = append(o.Labels.keys, key)
|
||||
o.Labels.vals = append(o.Labels.vals, val)
|
||||
}
|
||||
}
|
||||
|
||||
// Name sets the name
|
||||
func Name(n string) Option {
|
||||
return func(o *Options) {
|
||||
o.Name = n
|
||||
}
|
||||
}
|
||||
|
232
meter/wrapper/wrapper.go
Normal file
232
meter/wrapper/wrapper.go
Normal file
@@ -0,0 +1,232 @@
|
||||
// +build ignore
|
||||
|
||||
package wrapper
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/unistack-org/micro/v3/client"
|
||||
"github.com/unistack-org/micro/v3/meter"
|
||||
"github.com/unistack-org/micro/v3/server"
|
||||
)
|
||||
|
||||
type Options struct {
|
||||
Meter meter.Meter
|
||||
Name string
|
||||
Version string
|
||||
ID string
|
||||
}
|
||||
|
||||
type Option func(*Options)
|
||||
|
||||
func ServiceName(name string) Option {
|
||||
return func(o *Options) {
|
||||
o.Name = name
|
||||
}
|
||||
}
|
||||
|
||||
func ServiceVersion(version string) Option {
|
||||
return func(o *Options) {
|
||||
o.Version = version
|
||||
}
|
||||
}
|
||||
|
||||
func ServiceID(id string) Option {
|
||||
return func(o *Options) {
|
||||
o.ID = id
|
||||
}
|
||||
}
|
||||
|
||||
func Meter(m meter.Meter) Option {
|
||||
return func(o *Options) {
|
||||
o.Meter = m
|
||||
}
|
||||
}
|
||||
|
||||
type wrapper struct {
|
||||
options Options
|
||||
callFunc client.CallFunc
|
||||
client.Client
|
||||
}
|
||||
|
||||
func NewClientWrapper(opts ...Option) client.Wrapper {
|
||||
return func(c client.Client) client.Client {
|
||||
handler := &wrapper{
|
||||
labels: labels,
|
||||
Client: c,
|
||||
}
|
||||
|
||||
return handler
|
||||
}
|
||||
}
|
||||
|
||||
func NewCallWrapper(opts ...Option) client.CallWrapper {
|
||||
labels := getLabels(opts...)
|
||||
|
||||
return func(fn client.CallFunc) client.CallFunc {
|
||||
handler := &wrapper{
|
||||
labels: labels,
|
||||
callFunc: fn,
|
||||
}
|
||||
|
||||
return handler.CallFunc
|
||||
}
|
||||
}
|
||||
|
||||
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())
|
||||
wlabels := append(w.labels, fmt.Sprintf(`%sendpoint="%s"`, DefaultLabelPrefix, endpoint))
|
||||
|
||||
timeCounterSummary := metrics.GetOrCreateSummary(getName("client_request_latency_microseconds", wlabels))
|
||||
timeCounterHistogram := metrics.GetOrCreateSummary(getName("client_request_duration_seconds", wlabels))
|
||||
|
||||
ts := time.Now()
|
||||
err := w.callFunc(ctx, addr, req, rsp, opts)
|
||||
te := time.Since(ts)
|
||||
|
||||
timeCounterSummary.Update(float64(te.Seconds()))
|
||||
timeCounterHistogram.Update(te.Seconds())
|
||||
if err == nil {
|
||||
metrics.GetOrCreateCounter(getName("client_request_total", append(wlabels, fmt.Sprintf(`%sstatus="success"`, DefaultLabelPrefix)))).Inc()
|
||||
} else {
|
||||
metrics.GetOrCreateCounter(getName("client_request_total", append(wlabels, fmt.Sprintf(`%sstatus="failure"`, DefaultLabelPrefix)))).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())
|
||||
wlabels := append(w.labels, fmt.Sprintf(`%sendpoint="%s"`, DefaultLabelPrefix, endpoint))
|
||||
|
||||
timeCounterSummary := metrics.GetOrCreateSummary(getName("client_request_latency_microseconds", wlabels))
|
||||
timeCounterHistogram := metrics.GetOrCreateSummary(getName("client_request_duration_seconds", wlabels))
|
||||
|
||||
ts := time.Now()
|
||||
err := w.Client.Call(ctx, req, rsp, opts...)
|
||||
te := time.Since(ts)
|
||||
|
||||
timeCounterSummary.Update(float64(te.Seconds()))
|
||||
timeCounterHistogram.Update(te.Seconds())
|
||||
if err == nil {
|
||||
metrics.GetOrCreateCounter(getName("client_request_total", append(wlabels, fmt.Sprintf(`%sstatus="success"`, DefaultLabelPrefix)))).Inc()
|
||||
} else {
|
||||
metrics.GetOrCreateCounter(getName("client_request_total", append(wlabels, fmt.Sprintf(`%sstatus="failure"`, DefaultLabelPrefix)))).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())
|
||||
wlabels := append(w.labels, fmt.Sprintf(`%sendpoint="%s"`, DefaultLabelPrefix, endpoint))
|
||||
|
||||
timeCounterSummary := metrics.GetOrCreateSummary(getName("client_request_latency_microseconds", wlabels))
|
||||
timeCounterHistogram := metrics.GetOrCreateSummary(getName("client_request_duration_seconds", wlabels))
|
||||
|
||||
ts := time.Now()
|
||||
stream, err := w.Client.Stream(ctx, req, opts...)
|
||||
te := time.Since(ts)
|
||||
|
||||
timeCounterSummary.Update(float64(te.Seconds()))
|
||||
timeCounterHistogram.Update(te.Seconds())
|
||||
if err == nil {
|
||||
metrics.GetOrCreateCounter(getName("client_request_total", append(wlabels, fmt.Sprintf(`%sstatus="success"`, DefaultLabelPrefix)))).Inc()
|
||||
} else {
|
||||
metrics.GetOrCreateCounter(getName("client_request_total", append(wlabels, fmt.Sprintf(`%sstatus="failure"`, DefaultLabelPrefix)))).Inc()
|
||||
}
|
||||
|
||||
return stream, err
|
||||
}
|
||||
|
||||
func (w *wrapper) Publish(ctx context.Context, p client.Message, opts ...client.PublishOption) error {
|
||||
endpoint := p.Topic()
|
||||
wlabels := append(w.labels, fmt.Sprintf(`%sendpoint="%s"`, DefaultLabelPrefix, endpoint))
|
||||
|
||||
timeCounterSummary := metrics.GetOrCreateSummary(getName("publish_message_latency_microseconds", wlabels))
|
||||
timeCounterHistogram := metrics.GetOrCreateSummary(getName("publish_message_duration_seconds", wlabels))
|
||||
|
||||
ts := time.Now()
|
||||
err := w.Client.Publish(ctx, p, opts...)
|
||||
te := time.Since(ts)
|
||||
|
||||
timeCounterSummary.Update(float64(te.Seconds()))
|
||||
timeCounterHistogram.Update(te.Seconds())
|
||||
if err == nil {
|
||||
metrics.GetOrCreateCounter(getName("publish_message_total", append(wlabels, fmt.Sprintf(`%sstatus="success"`, DefaultLabelPrefix)))).Inc()
|
||||
} else {
|
||||
metrics.GetOrCreateCounter(getName("publish_message_total", append(wlabels, fmt.Sprintf(`%sstatus="failure"`, DefaultLabelPrefix)))).Inc()
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
func NewHandlerWrapper(opts ...Option) server.HandlerWrapper {
|
||||
labels := getLabels(opts...)
|
||||
|
||||
handler := &wrapper{
|
||||
labels: labels,
|
||||
}
|
||||
|
||||
return handler.HandlerFunc
|
||||
}
|
||||
|
||||
func (w *wrapper) HandlerFunc(fn server.HandlerFunc) server.HandlerFunc {
|
||||
return func(ctx context.Context, req server.Request, rsp interface{}) error {
|
||||
endpoint := req.Endpoint()
|
||||
wlabels := append(w.labels, fmt.Sprintf(`%sendpoint="%s"`, DefaultLabelPrefix, endpoint))
|
||||
|
||||
timeCounterSummary := metrics.GetOrCreateSummary(getName("server_request_latency_microseconds", wlabels))
|
||||
timeCounterHistogram := metrics.GetOrCreateSummary(getName("server_request_duration_seconds", wlabels))
|
||||
|
||||
ts := time.Now()
|
||||
err := fn(ctx, req, rsp)
|
||||
te := time.Since(ts)
|
||||
|
||||
timeCounterSummary.Update(float64(te.Seconds()))
|
||||
timeCounterHistogram.Update(te.Seconds())
|
||||
if err == nil {
|
||||
metrics.GetOrCreateCounter(getName("server_request_total", append(wlabels, fmt.Sprintf(`%sstatus="success"`, DefaultLabelPrefix)))).Inc()
|
||||
} else {
|
||||
metrics.GetOrCreateCounter(getName("server_request_total", append(wlabels, fmt.Sprintf(`%sstatus="failure"`, DefaultLabelPrefix)))).Inc()
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
func NewSubscriberWrapper(opts ...Option) server.SubscriberWrapper {
|
||||
labels := getLabels(opts...)
|
||||
|
||||
handler := &wrapper{
|
||||
labels: labels,
|
||||
}
|
||||
|
||||
return handler.SubscriberFunc
|
||||
}
|
||||
|
||||
func (w *wrapper) SubscriberFunc(fn server.SubscriberFunc) server.SubscriberFunc {
|
||||
return func(ctx context.Context, msg server.Message) error {
|
||||
endpoint := msg.Topic()
|
||||
wlabels := append(w.labels, fmt.Sprintf(`%sendpoint="%s"`, DefaultLabelPrefix, endpoint))
|
||||
|
||||
timeCounterSummary := metrics.GetOrCreateSummary(getName("subscribe_message_latency_microseconds", wlabels))
|
||||
timeCounterHistogram := metrics.GetOrCreateSummary(getName("subscribe_message_duration_seconds", wlabels))
|
||||
|
||||
ts := time.Now()
|
||||
err := fn(ctx, msg)
|
||||
te := time.Since(ts)
|
||||
|
||||
timeCounterSummary.Update(float64(te.Seconds()))
|
||||
timeCounterHistogram.Update(te.Seconds())
|
||||
if err == nil {
|
||||
metrics.GetOrCreateCounter(getName("subscribe_message_total", append(wlabels, fmt.Sprintf(`%sstatus="success"`, DefaultLabelPrefix)))).Inc()
|
||||
} else {
|
||||
metrics.GetOrCreateCounter(getName("subscribe_message_total", append(wlabels, fmt.Sprintf(`%sstatus="failure"`, DefaultLabelPrefix)))).Inc()
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
}
|
119
micro.go
119
micro.go
@@ -1,119 +0,0 @@
|
||||
// Package micro is a pluggable framework for microservices
|
||||
package micro
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/unistack-org/micro/v3/broker"
|
||||
"github.com/unistack-org/micro/v3/client"
|
||||
"github.com/unistack-org/micro/v3/server"
|
||||
)
|
||||
|
||||
type serviceKey struct{}
|
||||
|
||||
// Service is an interface that wraps the lower level libraries
|
||||
// within micro. Its a convenience method for building
|
||||
// and initialising services.
|
||||
type Service interface {
|
||||
// The service name
|
||||
Name() string
|
||||
// Init initialises options
|
||||
Init(...Option) error
|
||||
// Options returns the current options
|
||||
Options() Options
|
||||
// Client is used to call services
|
||||
Client() client.Client
|
||||
// Server is for handling requests and events
|
||||
Server() server.Server
|
||||
// Broker is for broker usage
|
||||
Broker() broker.Broker
|
||||
// Run the service
|
||||
Run() error
|
||||
// The service implementation
|
||||
String() string
|
||||
}
|
||||
|
||||
// Function is a one time executing Service
|
||||
type Function interface {
|
||||
// Inherits Service interface
|
||||
Service
|
||||
// Done signals to complete execution
|
||||
Done() error
|
||||
// Handle registers an RPC handler
|
||||
Handle(v interface{}) error
|
||||
// Subscribe registers a subscriber
|
||||
Subscribe(topic string, v interface{}) error
|
||||
}
|
||||
|
||||
/*
|
||||
// Type Event is a future type for acting on asynchronous events
|
||||
type Event interface {
|
||||
// Publish publishes a message to the event topic
|
||||
Publish(ctx context.Context, msg interface{}, opts ...client.PublishOption) error
|
||||
// Subscribe to the event
|
||||
Subscribe(ctx context.Context, v in
|
||||
}
|
||||
|
||||
// Resource is a future type for defining dependencies
|
||||
type Resource interface {
|
||||
// Name of the resource
|
||||
Name() string
|
||||
// Type of resource
|
||||
Type() string
|
||||
// Method of creation
|
||||
Create() error
|
||||
}
|
||||
*/
|
||||
|
||||
// Event is used to publish messages to a topic
|
||||
type Event interface {
|
||||
// Publish publishes a message to the event topic
|
||||
Publish(ctx context.Context, msg interface{}, opts ...client.PublishOption) error
|
||||
}
|
||||
|
||||
var (
|
||||
// HeaderPrefix for all headers passed
|
||||
HeaderPrefix = "Micro-"
|
||||
)
|
||||
|
||||
// NewService creates and returns a new Service based on the packages within.
|
||||
func NewService(opts ...Option) Service {
|
||||
return newService(opts...)
|
||||
}
|
||||
|
||||
// FromContext retrieves a Service from the Context.
|
||||
func FromContext(ctx context.Context) (Service, bool) {
|
||||
if ctx == nil {
|
||||
return nil, false
|
||||
}
|
||||
s, ok := ctx.Value(serviceKey{}).(Service)
|
||||
return s, ok
|
||||
}
|
||||
|
||||
// NewContext returns a new Context with the Service embedded within it.
|
||||
func NewContext(ctx context.Context, s Service) context.Context {
|
||||
if ctx == nil {
|
||||
ctx = context.Background()
|
||||
}
|
||||
return context.WithValue(ctx, serviceKey{}, s)
|
||||
}
|
||||
|
||||
// NewFunction returns a new Function for a one time executing Service
|
||||
func NewFunction(opts ...Option) Function {
|
||||
return newFunction(opts...)
|
||||
}
|
||||
|
||||
// NewEvent creates a new event publisher
|
||||
func NewEvent(topic string, c client.Client) Event {
|
||||
return &event{c, topic}
|
||||
}
|
||||
|
||||
// RegisterHandler is syntactic sugar for registering a handler
|
||||
func RegisterHandler(s server.Server, h interface{}, opts ...server.HandlerOption) error {
|
||||
return s.Handle(s.NewHandler(h, opts...))
|
||||
}
|
||||
|
||||
// RegisterSubscriber is syntactic sugar for registering a subscriber
|
||||
func RegisterSubscriber(topic string, s server.Server, h interface{}, opts ...server.SubscriberOption) error {
|
||||
return s.Subscribe(s.NewSubscriber(topic, h, opts...))
|
||||
}
|
@@ -3,9 +3,11 @@ package network
|
||||
import (
|
||||
"github.com/google/uuid"
|
||||
"github.com/unistack-org/micro/v3/logger"
|
||||
"github.com/unistack-org/micro/v3/meter"
|
||||
"github.com/unistack-org/micro/v3/network/tunnel"
|
||||
"github.com/unistack-org/micro/v3/proxy"
|
||||
"github.com/unistack-org/micro/v3/router"
|
||||
"github.com/unistack-org/micro/v3/tracer"
|
||||
)
|
||||
|
||||
// Option func
|
||||
@@ -31,6 +33,10 @@ type Options struct {
|
||||
Proxy proxy.Proxy
|
||||
// Logger
|
||||
Logger logger.Logger
|
||||
// Meter
|
||||
Meter meter.Meter
|
||||
// Tracer
|
||||
Tracer tracer.Tracer
|
||||
}
|
||||
|
||||
// Id sets the id of the network node
|
||||
@@ -96,11 +102,34 @@ func Logger(l logger.Logger) Option {
|
||||
}
|
||||
}
|
||||
|
||||
// DefaultOptions returns network default options
|
||||
func DefaultOptions() Options {
|
||||
return Options{
|
||||
// Meter sets the meter
|
||||
func Meter(m meter.Meter) Option {
|
||||
return func(o *Options) {
|
||||
o.Meter = m
|
||||
}
|
||||
}
|
||||
|
||||
// Tracer to be used for tracing
|
||||
func Tracer(t tracer.Tracer) Option {
|
||||
return func(o *Options) {
|
||||
o.Tracer = t
|
||||
}
|
||||
}
|
||||
|
||||
// NewOptions returns network default options
|
||||
func NewOptions(opts ...Option) Options {
|
||||
options := Options{
|
||||
Id: uuid.New().String(),
|
||||
Name: "go.micro",
|
||||
Address: ":0",
|
||||
Logger: logger.DefaultLogger,
|
||||
Meter: meter.DefaultMeter,
|
||||
Tracer: tracer.DefaultTracer,
|
||||
}
|
||||
|
||||
for _, o := range opts {
|
||||
o(&options)
|
||||
}
|
||||
|
||||
return options
|
||||
}
|
||||
|
263
network/transport/memory.go
Normal file
263
network/transport/memory.go
Normal file
@@ -0,0 +1,263 @@
|
||||
package transport
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"math/rand"
|
||||
"net"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
maddr "github.com/unistack-org/micro/v3/util/addr"
|
||||
mnet "github.com/unistack-org/micro/v3/util/net"
|
||||
)
|
||||
|
||||
type memorySocket struct {
|
||||
recv chan *Message
|
||||
send chan *Message
|
||||
// sock exit
|
||||
exit chan bool
|
||||
// listener exit
|
||||
lexit chan bool
|
||||
|
||||
local string
|
||||
remote string
|
||||
|
||||
// for send/recv transport.Timeout
|
||||
timeout time.Duration
|
||||
ctx context.Context
|
||||
sync.RWMutex
|
||||
}
|
||||
|
||||
type memoryClient struct {
|
||||
*memorySocket
|
||||
opts DialOptions
|
||||
}
|
||||
|
||||
type memoryListener struct {
|
||||
addr string
|
||||
exit chan bool
|
||||
conn chan *memorySocket
|
||||
lopts ListenOptions
|
||||
topts Options
|
||||
sync.RWMutex
|
||||
ctx context.Context
|
||||
}
|
||||
|
||||
type memoryTransport struct {
|
||||
opts Options
|
||||
sync.RWMutex
|
||||
listeners map[string]*memoryListener
|
||||
}
|
||||
|
||||
func (ms *memorySocket) Recv(m *Message) error {
|
||||
ms.RLock()
|
||||
defer ms.RUnlock()
|
||||
|
||||
ctx := ms.ctx
|
||||
if ms.timeout > 0 {
|
||||
var cancel context.CancelFunc
|
||||
ctx, cancel = context.WithTimeout(ms.ctx, ms.timeout)
|
||||
defer cancel()
|
||||
}
|
||||
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return ctx.Err()
|
||||
case <-ms.exit:
|
||||
return errors.New("connection closed")
|
||||
case <-ms.lexit:
|
||||
return errors.New("server connection closed")
|
||||
case cm := <-ms.recv:
|
||||
*m = *cm
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (ms *memorySocket) Local() string {
|
||||
return ms.local
|
||||
}
|
||||
|
||||
func (ms *memorySocket) Remote() string {
|
||||
return ms.remote
|
||||
}
|
||||
|
||||
func (ms *memorySocket) Send(m *Message) error {
|
||||
ms.RLock()
|
||||
defer ms.RUnlock()
|
||||
|
||||
ctx := ms.ctx
|
||||
if ms.timeout > 0 {
|
||||
var cancel context.CancelFunc
|
||||
ctx, cancel = context.WithTimeout(ms.ctx, ms.timeout)
|
||||
defer cancel()
|
||||
}
|
||||
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return ctx.Err()
|
||||
case <-ms.exit:
|
||||
return errors.New("connection closed")
|
||||
case <-ms.lexit:
|
||||
return errors.New("server connection closed")
|
||||
case ms.send <- m:
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (ms *memorySocket) Close() error {
|
||||
ms.Lock()
|
||||
defer ms.Unlock()
|
||||
select {
|
||||
case <-ms.exit:
|
||||
return nil
|
||||
default:
|
||||
close(ms.exit)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *memoryListener) Addr() string {
|
||||
return m.addr
|
||||
}
|
||||
|
||||
func (m *memoryListener) Close() error {
|
||||
m.Lock()
|
||||
defer m.Unlock()
|
||||
select {
|
||||
case <-m.exit:
|
||||
return nil
|
||||
default:
|
||||
close(m.exit)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *memoryListener) Accept(fn func(Socket)) error {
|
||||
for {
|
||||
select {
|
||||
case <-m.exit:
|
||||
return nil
|
||||
case c := <-m.conn:
|
||||
go fn(&memorySocket{
|
||||
lexit: c.lexit,
|
||||
exit: c.exit,
|
||||
send: c.recv,
|
||||
recv: c.send,
|
||||
local: c.Remote(),
|
||||
remote: c.Local(),
|
||||
timeout: m.topts.Timeout,
|
||||
ctx: m.topts.Context,
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (m *memoryTransport) Dial(ctx context.Context, addr string, opts ...DialOption) (Client, error) {
|
||||
m.RLock()
|
||||
defer m.RUnlock()
|
||||
|
||||
listener, ok := m.listeners[addr]
|
||||
if !ok {
|
||||
return nil, errors.New("could not dial " + addr)
|
||||
}
|
||||
|
||||
options := NewDialOptions(opts...)
|
||||
|
||||
client := &memoryClient{
|
||||
&memorySocket{
|
||||
send: make(chan *Message),
|
||||
recv: make(chan *Message),
|
||||
exit: make(chan bool),
|
||||
lexit: listener.exit,
|
||||
local: addr,
|
||||
remote: addr,
|
||||
timeout: m.opts.Timeout,
|
||||
ctx: m.opts.Context,
|
||||
},
|
||||
options,
|
||||
}
|
||||
|
||||
// pseudo connect
|
||||
select {
|
||||
case <-listener.exit:
|
||||
return nil, errors.New("connection error")
|
||||
case listener.conn <- client.memorySocket:
|
||||
}
|
||||
|
||||
return client, nil
|
||||
}
|
||||
|
||||
func (m *memoryTransport) Listen(ctx context.Context, addr string, opts ...ListenOption) (Listener, error) {
|
||||
m.Lock()
|
||||
defer m.Unlock()
|
||||
|
||||
options := NewListenOptions(opts...)
|
||||
|
||||
host, port, err := net.SplitHostPort(addr)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
addr, err = maddr.Extract(host)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// if zero port then randomly assign one
|
||||
if len(port) > 0 && port == "0" {
|
||||
i := rand.Intn(20000)
|
||||
port = fmt.Sprintf("%d", 10000+i)
|
||||
}
|
||||
|
||||
// set addr with port
|
||||
addr = mnet.HostPort(addr, port)
|
||||
|
||||
if _, ok := m.listeners[addr]; ok {
|
||||
return nil, errors.New("already listening on " + addr)
|
||||
}
|
||||
|
||||
listener := &memoryListener{
|
||||
lopts: options,
|
||||
topts: m.opts,
|
||||
addr: addr,
|
||||
conn: make(chan *memorySocket),
|
||||
exit: make(chan bool),
|
||||
ctx: m.opts.Context,
|
||||
}
|
||||
|
||||
m.listeners[addr] = listener
|
||||
|
||||
return listener, nil
|
||||
}
|
||||
|
||||
func (m *memoryTransport) Init(opts ...Option) error {
|
||||
for _, o := range opts {
|
||||
o(&m.opts)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *memoryTransport) Options() Options {
|
||||
return m.opts
|
||||
}
|
||||
|
||||
func (m *memoryTransport) String() string {
|
||||
return "memory"
|
||||
}
|
||||
|
||||
func (m *memoryTransport) Name() string {
|
||||
return m.opts.Name
|
||||
}
|
||||
|
||||
func NewTransport(opts ...Option) Transport {
|
||||
options := NewOptions(opts...)
|
||||
|
||||
rand.Seed(time.Now().UnixNano())
|
||||
|
||||
return &memoryTransport{
|
||||
opts: options,
|
||||
listeners: make(map[string]*memoryListener),
|
||||
}
|
||||
}
|
93
network/transport/memory_test.go
Normal file
93
network/transport/memory_test.go
Normal file
@@ -0,0 +1,93 @@
|
||||
package transport
|
||||
|
||||
import (
|
||||
"context"
|
||||
"os"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestMemoryTransport(t *testing.T) {
|
||||
tr := NewTransport()
|
||||
ctx := context.Background()
|
||||
// bind / listen
|
||||
l, err := tr.Listen(ctx, "127.0.0.1:8080")
|
||||
if err != nil {
|
||||
t.Fatalf("Unexpected error listening %v", err)
|
||||
}
|
||||
defer l.Close()
|
||||
|
||||
// accept
|
||||
go func() {
|
||||
if err := l.Accept(func(sock Socket) {
|
||||
for {
|
||||
var m Message
|
||||
if err := sock.Recv(&m); err != nil {
|
||||
return
|
||||
}
|
||||
if len(os.Getenv("INTEGRATION_TESTS")) == 0 {
|
||||
t.Logf("Server Received %s", string(m.Body))
|
||||
}
|
||||
if err := sock.Send(&Message{
|
||||
Body: []byte(`pong`),
|
||||
}); err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
}); err != nil {
|
||||
t.Fatalf("Unexpected error accepting %v", err)
|
||||
}
|
||||
}()
|
||||
|
||||
// dial
|
||||
c, err := tr.Dial(ctx, "127.0.0.1:8080")
|
||||
if err != nil {
|
||||
t.Fatalf("Unexpected error dialing %v", err)
|
||||
}
|
||||
defer c.Close()
|
||||
|
||||
// send <=> receive
|
||||
for i := 0; i < 3; i++ {
|
||||
if err := c.Send(&Message{
|
||||
Body: []byte(`ping`),
|
||||
}); err != nil {
|
||||
return
|
||||
}
|
||||
var m Message
|
||||
if err := c.Recv(&m); err != nil {
|
||||
return
|
||||
}
|
||||
if len(os.Getenv("INTEGRATION_TESTS")) == 0 {
|
||||
t.Logf("Client Received %s", string(m.Body))
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func TestListener(t *testing.T) {
|
||||
tr := NewTransport()
|
||||
ctx := context.Background()
|
||||
// bind / listen on random port
|
||||
l, err := tr.Listen(ctx, ":0")
|
||||
if err != nil {
|
||||
t.Fatalf("Unexpected error listening %v", err)
|
||||
}
|
||||
defer l.Close()
|
||||
|
||||
// try again
|
||||
l2, err := tr.Listen(ctx, ":0")
|
||||
if err != nil {
|
||||
t.Fatalf("Unexpected error listening %v", err)
|
||||
}
|
||||
defer l2.Close()
|
||||
|
||||
// now make sure it still fails
|
||||
l3, err := tr.Listen(ctx, ":8080")
|
||||
if err != nil {
|
||||
t.Fatalf("Unexpected error listening %v", err)
|
||||
}
|
||||
defer l3.Close()
|
||||
|
||||
if _, err := tr.Listen(ctx, ":8080"); err == nil {
|
||||
t.Fatal("Expected error binding to :8080 got nil")
|
||||
}
|
||||
}
|
@@ -1,77 +0,0 @@
|
||||
package transport
|
||||
|
||||
import "context"
|
||||
|
||||
type noopTransport struct {
|
||||
opts Options
|
||||
}
|
||||
|
||||
// NewTransport creates new noop transport
|
||||
func NewTransport(opts ...Option) Transport {
|
||||
return &noopTransport{opts: NewOptions(opts...)}
|
||||
}
|
||||
|
||||
func (t *noopTransport) Init(opts ...Option) error {
|
||||
for _, o := range opts {
|
||||
o(&t.opts)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (t *noopTransport) Options() Options {
|
||||
return t.opts
|
||||
}
|
||||
|
||||
func (t *noopTransport) Dial(ctx context.Context, addr string, opts ...DialOption) (Client, error) {
|
||||
options := NewDialOptions(opts...)
|
||||
return &noopClient{opts: options}, nil
|
||||
}
|
||||
|
||||
func (t *noopTransport) Listen(ctx context.Context, addr string, opts ...ListenOption) (Listener, error) {
|
||||
options := NewListenOptions(opts...)
|
||||
return &noopListener{opts: options}, nil
|
||||
}
|
||||
|
||||
func (t *noopTransport) String() string {
|
||||
return "noop"
|
||||
}
|
||||
|
||||
type noopClient struct {
|
||||
opts DialOptions
|
||||
}
|
||||
|
||||
func (c *noopClient) Close() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *noopClient) Local() string {
|
||||
return ""
|
||||
}
|
||||
|
||||
func (c *noopClient) Remote() string {
|
||||
return ""
|
||||
}
|
||||
|
||||
func (c *noopClient) Recv(*Message) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *noopClient) Send(*Message) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
type noopListener struct {
|
||||
opts ListenOptions
|
||||
}
|
||||
|
||||
func (l *noopListener) Addr() string {
|
||||
return ""
|
||||
}
|
||||
|
||||
func (l *noopListener) Accept(fn func(Socket)) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (l *noopListener) Close() error {
|
||||
return nil
|
||||
}
|
@@ -7,9 +7,12 @@ import (
|
||||
|
||||
"github.com/unistack-org/micro/v3/codec"
|
||||
"github.com/unistack-org/micro/v3/logger"
|
||||
"github.com/unistack-org/micro/v3/meter"
|
||||
"github.com/unistack-org/micro/v3/tracer"
|
||||
)
|
||||
|
||||
type Options struct {
|
||||
Name string
|
||||
// Addrs is the list of intermediary addresses to connect to
|
||||
Addrs []string
|
||||
// Codec is the codec interface to use where headers are not supported
|
||||
@@ -26,6 +29,10 @@ type Options struct {
|
||||
Timeout time.Duration
|
||||
// Logger sets the logger
|
||||
Logger logger.Logger
|
||||
// Meter sets the meter
|
||||
Meter meter.Meter
|
||||
// Tracer
|
||||
Tracer tracer.Tracer
|
||||
// Other options for implementations of the interface
|
||||
// can be stored in a context
|
||||
Context context.Context
|
||||
@@ -35,6 +42,8 @@ type Options struct {
|
||||
func NewOptions(opts ...Option) Options {
|
||||
options := Options{
|
||||
Logger: logger.DefaultLogger,
|
||||
Meter: meter.DefaultMeter,
|
||||
Tracer: tracer.DefaultTracer,
|
||||
Context: context.Background(),
|
||||
}
|
||||
|
||||
@@ -112,6 +121,13 @@ func Logger(l logger.Logger) Option {
|
||||
}
|
||||
}
|
||||
|
||||
// Meter sets the meter
|
||||
func Meter(m meter.Meter) Option {
|
||||
return func(o *Options) {
|
||||
o.Meter = m
|
||||
}
|
||||
}
|
||||
|
||||
// Context sets the context
|
||||
func Context(ctx context.Context) Option {
|
||||
return func(o *Options) {
|
||||
@@ -134,14 +150,6 @@ func Timeout(t time.Duration) Option {
|
||||
}
|
||||
}
|
||||
|
||||
// Use secure communication. If TLSConfig is not specified we
|
||||
// use InsecureSkipVerify and generate a self signed cert
|
||||
func Secure(b bool) Option {
|
||||
return func(o *Options) {
|
||||
o.Secure = b
|
||||
}
|
||||
}
|
||||
|
||||
// TLSConfig to be used for the transport.
|
||||
func TLSConfig(t *tls.Config) Option {
|
||||
return func(o *Options) {
|
||||
@@ -162,3 +170,17 @@ func WithTimeout(d time.Duration) DialOption {
|
||||
o.Timeout = d
|
||||
}
|
||||
}
|
||||
|
||||
// Tracer to be used for tracing
|
||||
func Tracer(t tracer.Tracer) Option {
|
||||
return func(o *Options) {
|
||||
o.Tracer = t
|
||||
}
|
||||
}
|
||||
|
||||
// Name sets the name
|
||||
func Name(n string) Option {
|
||||
return func(o *Options) {
|
||||
o.Name = n
|
||||
}
|
||||
}
|
||||
|
@@ -41,6 +41,10 @@ func (t *tunBroker) Init(opts ...broker.Option) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (t *tunBroker) Name() string {
|
||||
return t.opts.Name
|
||||
}
|
||||
|
||||
func (t *tunBroker) Options() broker.Options {
|
||||
return t.opts
|
||||
}
|
||||
|
@@ -5,7 +5,9 @@ import (
|
||||
|
||||
"github.com/google/uuid"
|
||||
"github.com/unistack-org/micro/v3/logger"
|
||||
"github.com/unistack-org/micro/v3/meter"
|
||||
"github.com/unistack-org/micro/v3/network/transport"
|
||||
"github.com/unistack-org/micro/v3/tracer"
|
||||
)
|
||||
|
||||
var (
|
||||
@@ -20,6 +22,7 @@ type Option func(*Options)
|
||||
|
||||
// Options provides network configuration options
|
||||
type Options struct {
|
||||
Name string
|
||||
// Id is tunnel id
|
||||
Id string
|
||||
// Address is tunnel address
|
||||
@@ -32,6 +35,10 @@ type Options struct {
|
||||
Transport transport.Transport
|
||||
// Logger
|
||||
Logger logger.Logger
|
||||
// Meter
|
||||
Meter meter.Meter
|
||||
// Tracer
|
||||
Tracer tracer.Tracer
|
||||
}
|
||||
|
||||
// DialOption func
|
||||
@@ -74,6 +81,13 @@ func Logger(l logger.Logger) Option {
|
||||
}
|
||||
}
|
||||
|
||||
// Meter sets the meter
|
||||
func Meter(m meter.Meter) Option {
|
||||
return func(o *Options) {
|
||||
o.Meter = m
|
||||
}
|
||||
}
|
||||
|
||||
// Address sets the tunnel address
|
||||
func Address(a string) Option {
|
||||
return func(o *Options) {
|
||||
@@ -152,9 +166,26 @@ func NewOptions(opts ...Option) Options {
|
||||
Id: uuid.New().String(),
|
||||
Address: DefaultAddress,
|
||||
Token: DefaultToken,
|
||||
Logger: logger.DefaultLogger,
|
||||
Meter: meter.DefaultMeter,
|
||||
Tracer: tracer.DefaultTracer,
|
||||
}
|
||||
for _, o := range opts {
|
||||
o(&options)
|
||||
}
|
||||
return options
|
||||
}
|
||||
|
||||
// Tracer to be used for tracing
|
||||
func Tracer(t tracer.Tracer) Option {
|
||||
return func(o *Options) {
|
||||
o.Tracer = t
|
||||
}
|
||||
}
|
||||
|
||||
// Name sets the name
|
||||
func Name(n string) Option {
|
||||
return func(o *Options) {
|
||||
o.Name = n
|
||||
}
|
||||
}
|
||||
|
644
options.go
644
options.go
@@ -2,37 +2,44 @@ package micro
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/unistack-org/micro/v3/auth"
|
||||
"github.com/unistack-org/micro/v3/broker"
|
||||
"github.com/unistack-org/micro/v3/client"
|
||||
"github.com/unistack-org/micro/v3/config"
|
||||
"github.com/unistack-org/micro/v3/debug/profile"
|
||||
"github.com/unistack-org/micro/v3/logger"
|
||||
"github.com/unistack-org/micro/v3/metadata"
|
||||
"github.com/unistack-org/micro/v3/registry"
|
||||
"github.com/unistack-org/micro/v3/meter"
|
||||
"github.com/unistack-org/micro/v3/register"
|
||||
"github.com/unistack-org/micro/v3/router"
|
||||
"github.com/unistack-org/micro/v3/runtime"
|
||||
"github.com/unistack-org/micro/v3/selector"
|
||||
"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/debug/profile"
|
||||
// "github.com/unistack-org/micro/v3/runtime"
|
||||
)
|
||||
|
||||
// Options for micro service
|
||||
type Options struct {
|
||||
Auth auth.Auth
|
||||
Broker broker.Broker
|
||||
Logger logger.Logger
|
||||
Configs []config.Config
|
||||
Client client.Client
|
||||
Server server.Server
|
||||
Store store.Store
|
||||
Registry registry.Registry
|
||||
Router router.Router
|
||||
Runtime runtime.Runtime
|
||||
Profile profile.Profile
|
||||
Name string
|
||||
Version string
|
||||
Metadata metadata.Metadata
|
||||
|
||||
Auths []auth.Auth
|
||||
Brokers []broker.Broker
|
||||
Loggers []logger.Logger
|
||||
Meters []meter.Meter
|
||||
Configs []config.Config
|
||||
Clients []client.Client
|
||||
Servers []server.Server
|
||||
Stores []store.Store
|
||||
Registers []register.Register
|
||||
Tracers []tracer.Tracer
|
||||
Routers []router.Router
|
||||
// Runtime runtime.Runtime
|
||||
// Profile profile.Profile
|
||||
|
||||
// Before and After funcs
|
||||
BeforeStart []func(context.Context) error
|
||||
@@ -48,16 +55,18 @@ type Options struct {
|
||||
// NewOptions returns new Options filled with defaults and overrided by provided opts
|
||||
func NewOptions(opts ...Option) Options {
|
||||
options := Options{
|
||||
Context: context.Background(),
|
||||
Server: server.DefaultServer,
|
||||
Client: client.DefaultClient,
|
||||
Broker: broker.DefaultBroker,
|
||||
Registry: registry.DefaultRegistry,
|
||||
Router: router.DefaultRouter,
|
||||
Auth: auth.DefaultAuth,
|
||||
Logger: logger.DefaultLogger,
|
||||
Configs: []config.Config{config.DefaultConfig},
|
||||
Store: store.DefaultStore,
|
||||
Context: context.Background(),
|
||||
Servers: []server.Server{server.DefaultServer},
|
||||
Clients: []client.Client{client.DefaultClient},
|
||||
Brokers: []broker.Broker{broker.DefaultBroker},
|
||||
Registers: []register.Register{register.DefaultRegister},
|
||||
Routers: []router.Router{router.DefaultRouter},
|
||||
Auths: []auth.Auth{auth.DefaultAuth},
|
||||
Loggers: []logger.Logger{logger.DefaultLogger},
|
||||
Tracers: []tracer.Tracer{tracer.DefaultTracer},
|
||||
Meters: []meter.Meter{meter.DefaultMeter},
|
||||
Configs: []config.Config{config.DefaultConfig},
|
||||
Stores: []store.Store{store.DefaultStore},
|
||||
//Runtime runtime.Runtime
|
||||
//Profile profile.Profile
|
||||
}
|
||||
@@ -70,264 +79,599 @@ func NewOptions(opts ...Option) Options {
|
||||
}
|
||||
|
||||
// Option func
|
||||
type Option func(*Options)
|
||||
type Option func(*Options) error
|
||||
|
||||
// Broker to be used for service
|
||||
func Broker(b broker.Broker) Option {
|
||||
return func(o *Options) {
|
||||
o.Broker = b
|
||||
if o.Client != nil {
|
||||
// Update Client and Server
|
||||
o.Client.Init(client.Broker(b))
|
||||
// Broker to be used for client and server
|
||||
func Broker(b broker.Broker, opts ...BrokerOption) Option {
|
||||
return func(o *Options) error {
|
||||
var err error
|
||||
bopts := brokerOptions{}
|
||||
for _, opt := range opts {
|
||||
opt(&bopts)
|
||||
}
|
||||
if o.Server != nil {
|
||||
o.Server.Init(server.Broker(b))
|
||||
all := false
|
||||
if len(opts) == 0 {
|
||||
all = true
|
||||
}
|
||||
for _, srv := range o.Servers {
|
||||
for _, os := range bopts.servers {
|
||||
if srv.Name() == os || all {
|
||||
if err = srv.Init(server.Broker(b)); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
for _, cli := range o.Clients {
|
||||
for _, oc := range bopts.clients {
|
||||
if cli.Name() == oc || all {
|
||||
if err = cli.Init(client.Broker(b)); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
type brokerOptions struct {
|
||||
servers []string
|
||||
clients []string
|
||||
}
|
||||
|
||||
type BrokerOption func(*brokerOptions)
|
||||
|
||||
func BrokerClient(n string) BrokerOption {
|
||||
return func(o *brokerOptions) {
|
||||
o.clients = append(o.clients, n)
|
||||
}
|
||||
}
|
||||
|
||||
func BrokerServer(n string) BrokerOption {
|
||||
return func(o *brokerOptions) {
|
||||
o.servers = append(o.servers, n)
|
||||
}
|
||||
}
|
||||
|
||||
// Client to be used for service
|
||||
func Client(c client.Client) Option {
|
||||
return func(o *Options) {
|
||||
o.Client = c
|
||||
func Client(c ...client.Client) Option {
|
||||
return func(o *Options) error {
|
||||
o.Clients = c
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// Clients to be used for service
|
||||
func Clients(c ...client.Client) Option {
|
||||
return func(o *Options) error {
|
||||
o.Clients = c
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// Context specifies a context for the service.
|
||||
// Can be used to signal shutdown of the service and for extra option values.
|
||||
func Context(ctx context.Context) Option {
|
||||
return func(o *Options) {
|
||||
return func(o *Options) error {
|
||||
// TODO: Pass context to underline stuff ?
|
||||
o.Context = ctx
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
// Profile to be used for debug profile
|
||||
func Profile(p profile.Profile) Option {
|
||||
return func(o *Options) {
|
||||
o.Profile = p
|
||||
}
|
||||
}
|
||||
*/
|
||||
|
||||
// Server to be used for service
|
||||
func Server(s server.Server) Option {
|
||||
return func(o *Options) {
|
||||
o.Server = s
|
||||
func Server(s ...server.Server) Option {
|
||||
return func(o *Options) error {
|
||||
o.Servers = s
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// Servers to be used for service
|
||||
func Servers(s ...server.Server) Option {
|
||||
return func(o *Options) error {
|
||||
o.Servers = s
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// Store sets the store to use
|
||||
func Store(s store.Store) Option {
|
||||
return func(o *Options) {
|
||||
o.Store = s
|
||||
func Store(s ...store.Store) Option {
|
||||
return func(o *Options) error {
|
||||
o.Stores = s
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// Stores sets the store to use
|
||||
func Stores(s ...store.Store) Option {
|
||||
return func(o *Options) error {
|
||||
o.Stores = s
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// Logger set the logger to use
|
||||
func Logger(l logger.Logger) Option {
|
||||
return func(o *Options) {
|
||||
o.Logger = l
|
||||
func Logger(l logger.Logger, opts ...LoggerOption) Option {
|
||||
return func(o *Options) error {
|
||||
var err error
|
||||
lopts := loggerOptions{}
|
||||
for _, opt := range opts {
|
||||
opt(&lopts)
|
||||
}
|
||||
all := false
|
||||
if len(opts) == 0 {
|
||||
all = true
|
||||
}
|
||||
for _, srv := range o.Servers {
|
||||
for _, os := range lopts.servers {
|
||||
if srv.Name() == os || all {
|
||||
if err = srv.Init(server.Logger(l)); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
for _, cli := range o.Clients {
|
||||
for _, oc := range lopts.clients {
|
||||
if cli.Name() == oc || all {
|
||||
if err = cli.Init(client.Logger(l)); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
for _, brk := range o.Brokers {
|
||||
for _, ob := range lopts.brokers {
|
||||
if brk.Name() == ob || all {
|
||||
if err = brk.Init(broker.Logger(l)); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
for _, reg := range o.Registers {
|
||||
for _, or := range lopts.registers {
|
||||
if reg.Name() == or || all {
|
||||
if err = reg.Init(register.Logger(l)); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
for _, str := range o.Stores {
|
||||
for _, or := range lopts.stores {
|
||||
if str.Name() == or || all {
|
||||
if err = str.Init(store.Logger(l)); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
for _, mtr := range o.Meters {
|
||||
for _, or := range lopts.meters {
|
||||
if mtr.Name() == or || all {
|
||||
if err = mtr.Init(meter.Logger(l)); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
for _, trc := range o.Tracers {
|
||||
for _, ot := range lopts.tracers {
|
||||
if trc.Name() == ot || all {
|
||||
if err = trc.Init(tracer.Logger(l)); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// Registry sets the registry for the service
|
||||
type LoggerOption func(*loggerOptions)
|
||||
|
||||
type loggerOptions struct {
|
||||
servers []string
|
||||
clients []string
|
||||
brokers []string
|
||||
registers []string
|
||||
stores []string
|
||||
meters []string
|
||||
tracers []string
|
||||
}
|
||||
|
||||
/*
|
||||
func LoggerServer(n string) LoggerOption {
|
||||
|
||||
}
|
||||
*/
|
||||
|
||||
// Meter set the meter to use
|
||||
func Meter(m ...meter.Meter) Option {
|
||||
return func(o *Options) error {
|
||||
o.Meters = m
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// Meters set the meter to use
|
||||
func Meters(m ...meter.Meter) Option {
|
||||
return func(o *Options) error {
|
||||
o.Meters = m
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// Register sets the register for the service
|
||||
// and the underlying components
|
||||
func Registry(r registry.Registry) Option {
|
||||
return func(o *Options) {
|
||||
o.Registry = r
|
||||
if o.Router != nil {
|
||||
// Update router
|
||||
o.Router.Init(router.Registry(r))
|
||||
func Register(r register.Register, opts ...RegisterOption) Option {
|
||||
return func(o *Options) error {
|
||||
var err error
|
||||
ropts := registerOptions{}
|
||||
for _, opt := range opts {
|
||||
opt(&ropts)
|
||||
}
|
||||
if o.Server != nil {
|
||||
// Update server
|
||||
o.Server.Init(server.Registry(r))
|
||||
all := false
|
||||
if len(opts) == 0 {
|
||||
all = true
|
||||
}
|
||||
if o.Broker != nil {
|
||||
// Update Broker
|
||||
o.Broker.Init(broker.Registry(r))
|
||||
for _, rtr := range o.Routers {
|
||||
for _, os := range ropts.routers {
|
||||
if rtr.Name() == os || all {
|
||||
if err = rtr.Init(router.Register(r)); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
for _, srv := range o.Servers {
|
||||
for _, os := range ropts.servers {
|
||||
if srv.Name() == os || all {
|
||||
if err = srv.Init(server.Register(r)); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
for _, brk := range o.Brokers {
|
||||
for _, os := range ropts.brokers {
|
||||
if brk.Name() == os || all {
|
||||
if err = brk.Init(broker.Register(r)); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// Tracer sets the tracer for the service
|
||||
func Tracer(t tracer.Tracer) Option {
|
||||
return func(o *Options) {
|
||||
if o.Server != nil {
|
||||
//todo client trace
|
||||
o.Server.Init(server.Tracer(t))
|
||||
}
|
||||
type registerOptions struct {
|
||||
routers []string
|
||||
servers []string
|
||||
brokers []string
|
||||
}
|
||||
|
||||
type RegisterOption func(*registerOptions)
|
||||
|
||||
func RegisterRouter(n string) RegisterOption {
|
||||
return func(o *registerOptions) {
|
||||
o.routers = append(o.routers, n)
|
||||
}
|
||||
}
|
||||
|
||||
func RegisterServer(n string) RegisterOption {
|
||||
return func(o *registerOptions) {
|
||||
o.servers = append(o.servers, n)
|
||||
}
|
||||
}
|
||||
|
||||
func RegisterBroker(n string) RegisterOption {
|
||||
return func(o *registerOptions) {
|
||||
o.brokers = append(o.brokers, n)
|
||||
}
|
||||
}
|
||||
|
||||
func Tracer(t tracer.Tracer, opts ...TracerOption) Option {
|
||||
return func(o *Options) error {
|
||||
var err error
|
||||
topts := tracerOptions{}
|
||||
for _, opt := range opts {
|
||||
opt(&topts)
|
||||
}
|
||||
all := false
|
||||
if len(opts) == 0 {
|
||||
all = true
|
||||
}
|
||||
for _, srv := range o.Servers {
|
||||
for _, os := range topts.servers {
|
||||
if srv.Name() == os || all {
|
||||
if err = srv.Init(server.Tracer(t)); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
for _, cli := range o.Clients {
|
||||
for _, os := range topts.clients {
|
||||
if cli.Name() == os || all {
|
||||
if err = cli.Init(client.Tracer(t)); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
for _, str := range o.Stores {
|
||||
for _, os := range topts.stores {
|
||||
if str.Name() == os || all {
|
||||
if err = str.Init(store.Tracer(t)); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
for _, brk := range o.Brokers {
|
||||
for _, os := range topts.brokers {
|
||||
if brk.Name() == os || all {
|
||||
if err = brk.Init(broker.Tracer(t)); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
type tracerOptions struct {
|
||||
clients []string
|
||||
servers []string
|
||||
brokers []string
|
||||
stores []string
|
||||
}
|
||||
|
||||
type TracerOption func(*tracerOptions)
|
||||
|
||||
func TracerClient(n string) TracerOption {
|
||||
return func(o *tracerOptions) {
|
||||
o.clients = append(o.clients, n)
|
||||
}
|
||||
}
|
||||
|
||||
func TracerServer(n string) TracerOption {
|
||||
return func(o *tracerOptions) {
|
||||
o.servers = append(o.servers, n)
|
||||
}
|
||||
}
|
||||
|
||||
func TracerBroker(n string) TracerOption {
|
||||
return func(o *tracerOptions) {
|
||||
o.brokers = append(o.brokers, n)
|
||||
}
|
||||
}
|
||||
|
||||
func TracerStore(n string) TracerOption {
|
||||
return func(o *tracerOptions) {
|
||||
o.stores = append(o.stores, n)
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
// Auth sets the auth for the service
|
||||
func Auth(a auth.Auth) Option {
|
||||
return func(o *Options) {
|
||||
return func(o *Options) error {
|
||||
o.Auth = a
|
||||
if o.Server != nil {
|
||||
o.Server.Init(server.Auth(a))
|
||||
}
|
||||
return nil
|
||||
}
|
||||
}
|
||||
*/
|
||||
|
||||
// Config sets the config for the service
|
||||
func Config(c ...config.Config) Option {
|
||||
return func(o *Options) error {
|
||||
o.Configs = c
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// Configs sets the configs for the service
|
||||
func Configs(c ...config.Config) Option {
|
||||
return func(o *Options) {
|
||||
return func(o *Options) error {
|
||||
o.Configs = c
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
// Selector sets the selector for the service client
|
||||
func Selector(s selector.Selector) Option {
|
||||
return func(o *Options) {
|
||||
return func(o *Options) error {
|
||||
if o.Client != nil {
|
||||
o.Client.Init(client.Selector(s))
|
||||
}
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
*/
|
||||
/*
|
||||
// Runtime sets the runtime
|
||||
func Runtime(r runtime.Runtime) Option {
|
||||
return func(o *Options) {
|
||||
o.Runtime = r
|
||||
}
|
||||
}
|
||||
*/
|
||||
|
||||
// Router sets the router
|
||||
func Router(r router.Router) Option {
|
||||
return func(o *Options) {
|
||||
o.Router = r
|
||||
// Update client
|
||||
if o.Client != nil {
|
||||
o.Client.Init(client.Router(r))
|
||||
func Router(r router.Router, opts ...RouterOption) Option {
|
||||
return func(o *Options) error {
|
||||
var err error
|
||||
ropts := routerOptions{}
|
||||
for _, opt := range opts {
|
||||
opt(&ropts)
|
||||
}
|
||||
all := false
|
||||
if len(opts) == 0 {
|
||||
all = true
|
||||
}
|
||||
for _, cli := range o.Clients {
|
||||
for _, os := range ropts.clients {
|
||||
if cli.Name() == os || all {
|
||||
if err = cli.Init(client.Router(r)); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
type routerOptions struct {
|
||||
clients []string
|
||||
}
|
||||
|
||||
type RouterOption func(*routerOptions)
|
||||
|
||||
func RouterClient(n string) RouterOption {
|
||||
return func(o *routerOptions) {
|
||||
o.clients = append(o.clients, n)
|
||||
}
|
||||
}
|
||||
|
||||
// Address sets the address of the server
|
||||
func Address(addr string) Option {
|
||||
return func(o *Options) {
|
||||
if o.Server != nil {
|
||||
o.Server.Init(server.Address(addr))
|
||||
return func(o *Options) error {
|
||||
switch len(o.Servers) {
|
||||
case 0:
|
||||
return fmt.Errorf("cant set address on nil server")
|
||||
case 1:
|
||||
break
|
||||
default:
|
||||
return fmt.Errorf("cant set same address for multiple servers")
|
||||
}
|
||||
return o.Servers[0].Init(server.Address(addr))
|
||||
}
|
||||
}
|
||||
|
||||
// Name of the service
|
||||
func Name(n string) Option {
|
||||
return func(o *Options) {
|
||||
if o.Server != nil {
|
||||
o.Server.Init(server.Name(n))
|
||||
}
|
||||
return func(o *Options) error {
|
||||
o.Name = n
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// Version of the service
|
||||
func Version(v string) Option {
|
||||
return func(o *Options) {
|
||||
if o.Server != nil {
|
||||
o.Server.Init(server.Version(v))
|
||||
}
|
||||
return func(o *Options) error {
|
||||
o.Version = v
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// Metadata associated with the service
|
||||
func Metadata(md metadata.Metadata) Option {
|
||||
return func(o *Options) {
|
||||
if o.Server != nil {
|
||||
o.Server.Init(server.Metadata(md))
|
||||
}
|
||||
return func(o *Options) error {
|
||||
o.Metadata = metadata.Copy(md)
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// RegisterTTL specifies the TTL to use when registering the service
|
||||
func RegisterTTL(t time.Duration) Option {
|
||||
return func(o *Options) {
|
||||
if o.Server != nil {
|
||||
o.Server.Init(server.RegisterTTL(t))
|
||||
func RegisterTTL(td time.Duration, opts ...RegisterOption) Option {
|
||||
return func(o *Options) error {
|
||||
var err error
|
||||
ropts := registerOptions{}
|
||||
for _, opt := range opts {
|
||||
opt(&ropts)
|
||||
}
|
||||
all := false
|
||||
if len(opts) == 0 {
|
||||
all = true
|
||||
}
|
||||
for _, srv := range o.Servers {
|
||||
for _, os := range ropts.servers {
|
||||
if srv.Name() == os || all {
|
||||
if err = srv.Init(server.RegisterTTL(td)); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// RegisterInterval specifies the interval on which to re-register
|
||||
func RegisterInterval(t time.Duration) Option {
|
||||
return func(o *Options) {
|
||||
if o.Server != nil {
|
||||
o.Server.Init(server.RegisterInterval(t))
|
||||
func RegisterInterval(td time.Duration, opts ...RegisterOption) Option {
|
||||
return func(o *Options) error {
|
||||
var err error
|
||||
ropts := registerOptions{}
|
||||
for _, opt := range opts {
|
||||
opt(&ropts)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// WrapClient is a convenience method for wrapping a Client with
|
||||
// some middleware component. A list of wrappers can be provided.
|
||||
// Wrappers are applied in reverse order so the last is executed first.
|
||||
func WrapClient(w ...client.Wrapper) Option {
|
||||
return func(o *Options) {
|
||||
// apply in reverse
|
||||
for i := len(w); i > 0; i-- {
|
||||
o.Client = w[i-1](o.Client)
|
||||
all := false
|
||||
if len(opts) == 0 {
|
||||
all = true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// WrapCall is a convenience method for wrapping a Client CallFunc
|
||||
func WrapCall(w ...client.CallWrapper) Option {
|
||||
return func(o *Options) {
|
||||
o.Client.Init(client.WrapCall(w...))
|
||||
}
|
||||
}
|
||||
|
||||
// WrapHandler adds a handler Wrapper to a list of options passed into the server
|
||||
func WrapHandler(w ...server.HandlerWrapper) Option {
|
||||
return func(o *Options) {
|
||||
var wrappers []server.Option
|
||||
|
||||
for _, wrap := range w {
|
||||
wrappers = append(wrappers, server.WrapHandler(wrap))
|
||||
for _, srv := range o.Servers {
|
||||
for _, os := range ropts.servers {
|
||||
if srv.Name() == os || all {
|
||||
if err = srv.Init(server.RegisterInterval(td)); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Init once
|
||||
o.Server.Init(wrappers...)
|
||||
}
|
||||
}
|
||||
|
||||
// WrapSubscriber adds a subscriber Wrapper to a list of options passed into the server
|
||||
func WrapSubscriber(w ...server.SubscriberWrapper) Option {
|
||||
return func(o *Options) {
|
||||
var wrappers []server.Option
|
||||
|
||||
for _, wrap := range w {
|
||||
wrappers = append(wrappers, server.WrapSubscriber(wrap))
|
||||
}
|
||||
|
||||
// Init once
|
||||
o.Server.Init(wrappers...)
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// BeforeStart run funcs before service starts
|
||||
func BeforeStart(fn func(context.Context) error) Option {
|
||||
return func(o *Options) {
|
||||
return func(o *Options) error {
|
||||
o.BeforeStart = append(o.BeforeStart, fn)
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// BeforeStop run funcs before service stops
|
||||
func BeforeStop(fn func(context.Context) error) Option {
|
||||
return func(o *Options) {
|
||||
return func(o *Options) error {
|
||||
o.BeforeStop = append(o.BeforeStop, fn)
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// AfterStart run funcs after service starts
|
||||
func AfterStart(fn func(context.Context) error) Option {
|
||||
return func(o *Options) {
|
||||
return func(o *Options) error {
|
||||
o.AfterStart = append(o.AfterStart, fn)
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// AfterStop run funcs after service stops
|
||||
func AfterStop(fn func(context.Context) error) Option {
|
||||
return func(o *Options) {
|
||||
return func(o *Options) error {
|
||||
o.AfterStop = append(o.AfterStop, fn)
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
@@ -4,7 +4,9 @@ package proxy
|
||||
import (
|
||||
"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/router"
|
||||
"github.com/unistack-org/micro/v3/tracer"
|
||||
)
|
||||
|
||||
// Options for proxy
|
||||
@@ -19,11 +21,29 @@ type Options struct {
|
||||
Links map[string]client.Client
|
||||
// Logger
|
||||
Logger logger.Logger
|
||||
// Meter
|
||||
Meter meter.Meter
|
||||
// Tracer
|
||||
Tracer tracer.Tracer
|
||||
}
|
||||
|
||||
// Option func signature
|
||||
type Option func(o *Options)
|
||||
|
||||
func NewOptions(opts ...Option) Options {
|
||||
options := Options{
|
||||
Logger: logger.DefaultLogger,
|
||||
Meter: meter.DefaultMeter,
|
||||
Tracer: tracer.DefaultTracer,
|
||||
}
|
||||
|
||||
for _, o := range opts {
|
||||
o(&options)
|
||||
}
|
||||
|
||||
return options
|
||||
}
|
||||
|
||||
// WithEndpoint sets a proxy endpoint
|
||||
func WithEndpoint(e string) Option {
|
||||
return func(o *Options) {
|
||||
@@ -52,6 +72,13 @@ func WithLogger(l logger.Logger) Option {
|
||||
}
|
||||
}
|
||||
|
||||
// WithMeter specifies the meter to use
|
||||
func WithMeter(m meter.Meter) Option {
|
||||
return func(o *Options) {
|
||||
o.Meter = m
|
||||
}
|
||||
}
|
||||
|
||||
// WithLink sets a link for outbound requests
|
||||
func WithLink(name string, c client.Client) Option {
|
||||
return func(o *Options) {
|
||||
@@ -61,3 +88,10 @@ func WithLink(name string, c client.Client) Option {
|
||||
o.Links[name] = c
|
||||
}
|
||||
}
|
||||
|
||||
// Tracer to be used for tracing
|
||||
func Tracer(t tracer.Tracer) Option {
|
||||
return func(o *Options) {
|
||||
o.Tracer = t
|
||||
}
|
||||
}
|
||||
|
@@ -1,26 +1,26 @@
|
||||
package registry
|
||||
package register
|
||||
|
||||
import (
|
||||
"context"
|
||||
)
|
||||
|
||||
type registryKey struct{}
|
||||
type registerKey struct{}
|
||||
|
||||
// FromContext get registry from context
|
||||
func FromContext(ctx context.Context) (Registry, bool) {
|
||||
// FromContext get register from context
|
||||
func FromContext(ctx context.Context) (Register, bool) {
|
||||
if ctx == nil {
|
||||
return nil, false
|
||||
}
|
||||
c, ok := ctx.Value(registryKey{}).(Registry)
|
||||
c, ok := ctx.Value(registerKey{}).(Register)
|
||||
return c, ok
|
||||
}
|
||||
|
||||
// NewContext put registry in context
|
||||
func NewContext(ctx context.Context, c Registry) context.Context {
|
||||
// NewContext put register in context
|
||||
func NewContext(ctx context.Context, c Register) context.Context {
|
||||
if ctx == nil {
|
||||
ctx = context.Background()
|
||||
}
|
||||
return context.WithValue(ctx, registryKey{}, c)
|
||||
return context.WithValue(ctx, registerKey{}, c)
|
||||
}
|
||||
|
||||
// SetOption returns a function to setup a context with given value
|
@@ -1,4 +1,4 @@
|
||||
package registry
|
||||
package register
|
||||
|
||||
import (
|
||||
"fmt"
|
@@ -1,4 +1,4 @@
|
||||
package registry
|
||||
package register
|
||||
|
||||
import (
|
||||
"context"
|
541
register/memory.go
Normal file
541
register/memory.go
Normal file
@@ -0,0 +1,541 @@
|
||||
package register
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/google/uuid"
|
||||
"github.com/unistack-org/micro/v3/logger"
|
||||
)
|
||||
|
||||
var (
|
||||
sendEventTime = 10 * time.Millisecond
|
||||
ttlPruneTime = time.Second
|
||||
)
|
||||
|
||||
type node struct {
|
||||
*Node
|
||||
TTL time.Duration
|
||||
LastSeen time.Time
|
||||
}
|
||||
|
||||
type record struct {
|
||||
Name string
|
||||
Version string
|
||||
Metadata map[string]string
|
||||
Nodes map[string]*node
|
||||
Endpoints []*Endpoint
|
||||
}
|
||||
|
||||
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
|
||||
sync.RWMutex
|
||||
}
|
||||
|
||||
// services is a KV map with service name as the key and a map of records as the value
|
||||
type services map[string]map[string]*record
|
||||
|
||||
// NewRegister returns an initialized in-memory register
|
||||
func NewRegister(opts ...Option) Register {
|
||||
r := &memory{
|
||||
opts: NewOptions(opts...),
|
||||
records: make(map[string]services),
|
||||
watchers: make(map[string]*watcher),
|
||||
}
|
||||
|
||||
go r.ttlPrune()
|
||||
|
||||
return r
|
||||
}
|
||||
|
||||
func (m *memory) ttlPrune() {
|
||||
prune := time.NewTicker(ttlPruneTime)
|
||||
defer prune.Stop()
|
||||
|
||||
for {
|
||||
select {
|
||||
case <-prune.C:
|
||||
m.Lock()
|
||||
for domain, services := range m.records {
|
||||
for service, versions := range services {
|
||||
for version, record := range versions {
|
||||
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)
|
||||
}
|
||||
delete(m.records[domain][service][version].Nodes, id)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
m.Unlock()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (m *memory) sendEvent(r *Result) {
|
||||
m.RLock()
|
||||
watchers := make([]*watcher, 0, len(m.watchers))
|
||||
for _, w := range m.watchers {
|
||||
watchers = append(watchers, w)
|
||||
}
|
||||
m.RUnlock()
|
||||
|
||||
for _, w := range watchers {
|
||||
select {
|
||||
case <-w.exit:
|
||||
m.Lock()
|
||||
delete(m.watchers, w.id)
|
||||
m.Unlock()
|
||||
default:
|
||||
select {
|
||||
case w.res <- r:
|
||||
case <-time.After(sendEventTime):
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (m *memory) Connect(ctx context.Context) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *memory) Disconnect(ctx context.Context) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *memory) Init(opts ...Option) error {
|
||||
for _, o := range opts {
|
||||
o(&m.opts)
|
||||
}
|
||||
|
||||
// add services
|
||||
m.Lock()
|
||||
defer m.Unlock()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *memory) Options() Options {
|
||||
return m.opts
|
||||
}
|
||||
|
||||
func (m *memory) Register(ctx context.Context, s *Service, opts ...RegisterOption) error {
|
||||
m.Lock()
|
||||
defer m.Unlock()
|
||||
|
||||
options := NewRegisterOptions(opts...)
|
||||
|
||||
// get the services for this domain from the register
|
||||
srvs, ok := m.records[options.Domain]
|
||||
if !ok {
|
||||
srvs = make(services)
|
||||
}
|
||||
|
||||
// domain is set in metadata so it can be passed to watchers
|
||||
if s.Metadata == nil {
|
||||
s.Metadata = map[string]string{"domain": options.Domain}
|
||||
} else {
|
||||
s.Metadata["domain"] = options.Domain
|
||||
}
|
||||
|
||||
// ensure the service name exists
|
||||
r := serviceToRecord(s, options.TTL)
|
||||
if _, ok := srvs[s.Name]; !ok {
|
||||
srvs[s.Name] = make(map[string]*record)
|
||||
}
|
||||
|
||||
if _, ok := srvs[s.Name][s.Version]; !ok {
|
||||
srvs[s.Name][s.Version] = r
|
||||
if m.opts.Logger.V(logger.DebugLevel) {
|
||||
m.opts.Logger.Debugf(m.opts.Context, "Register added new service: %s, version: %s", s.Name, s.Version)
|
||||
}
|
||||
m.records[options.Domain] = srvs
|
||||
go m.sendEvent(&Result{Action: "create", Service: s})
|
||||
}
|
||||
|
||||
var addedNodes bool
|
||||
|
||||
for _, n := range s.Nodes {
|
||||
// check if already exists
|
||||
if _, ok := srvs[s.Name][s.Version].Nodes[n.Id]; ok {
|
||||
continue
|
||||
}
|
||||
|
||||
metadata := make(map[string]string)
|
||||
|
||||
// make copy of metadata
|
||||
for k, v := range n.Metadata {
|
||||
metadata[k] = v
|
||||
}
|
||||
|
||||
// set the domain
|
||||
metadata["domain"] = options.Domain
|
||||
|
||||
// add the node
|
||||
srvs[s.Name][s.Version].Nodes[n.Id] = &node{
|
||||
Node: &Node{
|
||||
Id: n.Id,
|
||||
Address: n.Address,
|
||||
Metadata: metadata,
|
||||
},
|
||||
TTL: options.TTL,
|
||||
LastSeen: time.Now(),
|
||||
}
|
||||
|
||||
addedNodes = true
|
||||
}
|
||||
|
||||
if addedNodes {
|
||||
if m.opts.Logger.V(logger.DebugLevel) {
|
||||
m.opts.Logger.Debugf(m.opts.Context, "Register added new node to service: %s, version: %s", s.Name, s.Version)
|
||||
}
|
||||
go m.sendEvent(&Result{Action: "update", Service: s})
|
||||
} else {
|
||||
// refresh TTL and timestamp
|
||||
for _, n := range s.Nodes {
|
||||
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()
|
||||
}
|
||||
}
|
||||
|
||||
m.records[options.Domain] = srvs
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *memory) Deregister(ctx context.Context, s *Service, opts ...DeregisterOption) error {
|
||||
m.Lock()
|
||||
defer m.Unlock()
|
||||
|
||||
options := NewDeregisterOptions(opts...)
|
||||
|
||||
// domain is set in metadata so it can be passed to watchers
|
||||
if s.Metadata == nil {
|
||||
s.Metadata = map[string]string{"domain": options.Domain}
|
||||
} else {
|
||||
s.Metadata["domain"] = options.Domain
|
||||
}
|
||||
|
||||
// if the domain doesn't exist, there is nothing to deregister
|
||||
services, ok := m.records[options.Domain]
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
|
||||
// if no services with this name and version exist, there is nothing to deregister
|
||||
versions, ok := services[s.Name]
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
|
||||
version, ok := versions[s.Version]
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
|
||||
// deregister all of the service nodes from this version
|
||||
for _, n := range s.Nodes {
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
// if the nodes not empty, we replace the version in the store and exist, the rest of the logic
|
||||
// is cleanup
|
||||
if len(version.Nodes) > 0 {
|
||||
m.records[options.Domain][s.Name][s.Version] = version
|
||||
go m.sendEvent(&Result{Action: "update", Service: s})
|
||||
return nil
|
||||
}
|
||||
|
||||
// if this version was the only version of the service, we can remove the whole service from the
|
||||
// register and exit
|
||||
if len(versions) == 1 {
|
||||
delete(m.records[options.Domain], s.Name)
|
||||
go m.sendEvent(&Result{Action: "delete", Service: s})
|
||||
|
||||
if m.opts.Logger.V(logger.DebugLevel) {
|
||||
m.opts.Logger.Debugf(m.opts.Context, "Register removed service: %s", s.Name)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// there are other versions of the service running, so only remove this version of it
|
||||
delete(m.records[options.Domain][s.Name], s.Version)
|
||||
go m.sendEvent(&Result{Action: "delete", Service: s})
|
||||
if m.opts.Logger.V(logger.DebugLevel) {
|
||||
m.opts.Logger.Debugf(m.opts.Context, "Register removed service: %s, version: %s", s.Name, s.Version)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *memory) LookupService(ctx context.Context, name string, opts ...LookupOption) ([]*Service, error) {
|
||||
options := NewLookupOptions(opts...)
|
||||
|
||||
// if it's a wildcard domain, return from all domains
|
||||
if options.Domain == WildcardDomain {
|
||||
m.RLock()
|
||||
recs := m.records
|
||||
m.RUnlock()
|
||||
|
||||
var services []*Service
|
||||
|
||||
for domain := range recs {
|
||||
srvs, err := m.LookupService(ctx, name, append(opts, LookupDomain(domain))...)
|
||||
if err == ErrNotFound {
|
||||
continue
|
||||
} else if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
services = append(services, srvs...)
|
||||
}
|
||||
|
||||
if len(services) == 0 {
|
||||
return nil, ErrNotFound
|
||||
}
|
||||
return services, nil
|
||||
}
|
||||
|
||||
m.RLock()
|
||||
defer m.RUnlock()
|
||||
|
||||
// check the domain exists
|
||||
services, ok := m.records[options.Domain]
|
||||
if !ok {
|
||||
return nil, ErrNotFound
|
||||
}
|
||||
|
||||
// check the service exists
|
||||
versions, ok := services[name]
|
||||
if !ok || len(versions) == 0 {
|
||||
return nil, ErrNotFound
|
||||
}
|
||||
|
||||
// serialize the response
|
||||
result := make([]*Service, len(versions))
|
||||
|
||||
var i int
|
||||
|
||||
for _, r := range versions {
|
||||
result[i] = recordToService(r, options.Domain)
|
||||
i++
|
||||
}
|
||||
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func (m *memory) ListServices(ctx context.Context, opts ...ListOption) ([]*Service, error) {
|
||||
options := NewListOptions(opts...)
|
||||
|
||||
// if it's a wildcard domain, list from all domains
|
||||
if options.Domain == WildcardDomain {
|
||||
m.RLock()
|
||||
recs := m.records
|
||||
m.RUnlock()
|
||||
|
||||
var services []*Service
|
||||
|
||||
for domain := range recs {
|
||||
srvs, err := m.ListServices(ctx, append(opts, ListDomain(domain))...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
services = append(services, srvs...)
|
||||
}
|
||||
|
||||
return services, nil
|
||||
}
|
||||
|
||||
m.RLock()
|
||||
defer m.RUnlock()
|
||||
|
||||
// ensure the domain exists
|
||||
services, ok := m.records[options.Domain]
|
||||
if !ok {
|
||||
return make([]*Service, 0), nil
|
||||
}
|
||||
|
||||
// serialize the result, each version counts as an individual service
|
||||
var result []*Service
|
||||
|
||||
for domain, service := range services {
|
||||
for _, version := range service {
|
||||
result = append(result, recordToService(version, domain))
|
||||
}
|
||||
}
|
||||
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func (m *memory) Watch(ctx context.Context, opts ...WatchOption) (Watcher, error) {
|
||||
wo := NewWatchOptions(opts...)
|
||||
|
||||
// construct the watcher
|
||||
w := &watcher{
|
||||
exit: make(chan bool),
|
||||
res: make(chan *Result),
|
||||
id: uuid.New().String(),
|
||||
wo: wo,
|
||||
}
|
||||
|
||||
m.Lock()
|
||||
m.watchers[w.id] = w
|
||||
m.Unlock()
|
||||
|
||||
return w, nil
|
||||
}
|
||||
|
||||
func (m *memory) Name() string {
|
||||
return m.opts.Name
|
||||
}
|
||||
|
||||
func (m *memory) String() string {
|
||||
return "memory"
|
||||
}
|
||||
|
||||
type watcher struct {
|
||||
id string
|
||||
wo WatchOptions
|
||||
res chan *Result
|
||||
exit chan bool
|
||||
}
|
||||
|
||||
func (m *watcher) Next() (*Result, error) {
|
||||
for {
|
||||
select {
|
||||
case r := <-m.res:
|
||||
if r.Service == nil {
|
||||
continue
|
||||
}
|
||||
|
||||
if len(m.wo.Service) > 0 && m.wo.Service != r.Service.Name {
|
||||
continue
|
||||
}
|
||||
|
||||
// extract domain from service metadata
|
||||
var domain string
|
||||
if r.Service.Metadata != nil && len(r.Service.Metadata["domain"]) > 0 {
|
||||
domain = r.Service.Metadata["domain"]
|
||||
} else {
|
||||
domain = DefaultDomain
|
||||
}
|
||||
|
||||
// only send the event if watching the wildcard or this specific domain
|
||||
if m.wo.Domain == WildcardDomain || m.wo.Domain == domain {
|
||||
return r, nil
|
||||
}
|
||||
case <-m.exit:
|
||||
return nil, errors.New("watcher stopped")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (m *watcher) Stop() {
|
||||
select {
|
||||
case <-m.exit:
|
||||
return
|
||||
default:
|
||||
close(m.exit)
|
||||
}
|
||||
}
|
||||
|
||||
func serviceToRecord(s *Service, ttl time.Duration) *record {
|
||||
metadata := make(map[string]string, len(s.Metadata))
|
||||
for k, v := range s.Metadata {
|
||||
metadata[k] = v
|
||||
}
|
||||
|
||||
nodes := make(map[string]*node, len(s.Nodes))
|
||||
for _, n := range s.Nodes {
|
||||
nodes[n.Id] = &node{
|
||||
Node: n,
|
||||
TTL: ttl,
|
||||
LastSeen: time.Now(),
|
||||
}
|
||||
}
|
||||
|
||||
endpoints := make([]*Endpoint, len(s.Endpoints))
|
||||
for i, e := range s.Endpoints {
|
||||
endpoints[i] = e
|
||||
}
|
||||
|
||||
return &record{
|
||||
Name: s.Name,
|
||||
Version: s.Version,
|
||||
Metadata: metadata,
|
||||
Nodes: nodes,
|
||||
Endpoints: endpoints,
|
||||
}
|
||||
}
|
||||
|
||||
func recordToService(r *record, domain string) *Service {
|
||||
metadata := make(map[string]string, len(r.Metadata))
|
||||
for k, v := range r.Metadata {
|
||||
metadata[k] = v
|
||||
}
|
||||
|
||||
// set the domain in metadata so it can be determined when a wildcard query is performed
|
||||
metadata["domain"] = domain
|
||||
|
||||
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))
|
||||
for k, v := range e.Metadata {
|
||||
metadata[k] = v
|
||||
}
|
||||
|
||||
endpoints[i] = &Endpoint{
|
||||
Name: e.Name,
|
||||
Request: request,
|
||||
Response: response,
|
||||
Metadata: metadata,
|
||||
}
|
||||
}
|
||||
|
||||
nodes := make([]*Node, len(r.Nodes))
|
||||
i := 0
|
||||
for _, n := range r.Nodes {
|
||||
metadata := make(map[string]string, len(n.Metadata))
|
||||
for k, v := range n.Metadata {
|
||||
metadata[k] = v
|
||||
}
|
||||
|
||||
nodes[i] = &Node{
|
||||
Id: n.Id,
|
||||
Address: n.Address,
|
||||
Metadata: metadata,
|
||||
}
|
||||
i++
|
||||
}
|
||||
|
||||
return &Service{
|
||||
Name: r.Name,
|
||||
Version: r.Version,
|
||||
Metadata: metadata,
|
||||
Endpoints: endpoints,
|
||||
Nodes: nodes,
|
||||
}
|
||||
}
|
313
register/memory_test.go
Normal file
313
register/memory_test.go
Normal file
@@ -0,0 +1,313 @@
|
||||
package register
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"os"
|
||||
"testing"
|
||||
"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",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
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",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
)
|
||||
|
||||
func TestMemoryRegistry(t *testing.T) {
|
||||
ctx := context.TODO()
|
||||
m := NewRegister()
|
||||
|
||||
fn := func(k string, v []*Service) {
|
||||
services, err := m.LookupService(ctx, k)
|
||||
if err != nil {
|
||||
t.Errorf("Unexpected error getting service %s: %v", k, err)
|
||||
}
|
||||
|
||||
if len(services) != len(v) {
|
||||
t.Errorf("Expected %d services for %s, got %d", len(v), k, len(services))
|
||||
}
|
||||
|
||||
for _, service := range v {
|
||||
var seen bool
|
||||
for _, s := range services {
|
||||
if s.Version == service.Version {
|
||||
seen = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !seen {
|
||||
t.Errorf("expected to find version %s", service.Version)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// register data
|
||||
for _, v := range testData {
|
||||
serviceCount := 0
|
||||
for _, service := range v {
|
||||
if err := m.Register(ctx, service); err != nil {
|
||||
t.Errorf("Unexpected register error: %v", err)
|
||||
}
|
||||
serviceCount++
|
||||
// after the service has been registered we should be able to query it
|
||||
services, err := m.LookupService(ctx, service.Name)
|
||||
if err != nil {
|
||||
t.Errorf("Unexpected error getting service %s: %v", service.Name, err)
|
||||
}
|
||||
if len(services) != serviceCount {
|
||||
t.Errorf("Expected %d services for %s, got %d", serviceCount, service.Name, len(services))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// using test data
|
||||
for k, v := range testData {
|
||||
fn(k, v)
|
||||
}
|
||||
|
||||
services, err := m.ListServices(ctx)
|
||||
if err != nil {
|
||||
t.Errorf("Unexpected error when listing services: %v", err)
|
||||
}
|
||||
|
||||
totalServiceCount := 0
|
||||
for _, testSvc := range testData {
|
||||
for range testSvc {
|
||||
totalServiceCount++
|
||||
}
|
||||
}
|
||||
|
||||
if len(services) != totalServiceCount {
|
||||
t.Errorf("Expected total service count: %d, got: %d", totalServiceCount, len(services))
|
||||
}
|
||||
|
||||
// deregister
|
||||
for _, v := range testData {
|
||||
for _, service := range v {
|
||||
if err := m.Deregister(ctx, service); err != nil {
|
||||
t.Errorf("Unexpected deregister error: %v", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// after all the service nodes have been deregistered we should not get any results
|
||||
for _, v := range testData {
|
||||
for _, service := range v {
|
||||
services, err := m.LookupService(ctx, service.Name)
|
||||
if err != ErrNotFound {
|
||||
t.Errorf("Expected error: %v, got: %v", ErrNotFound, err)
|
||||
}
|
||||
if len(services) != 0 {
|
||||
t.Errorf("Expected %d services for %s, got %d", 0, service.Name, len(services))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestMemoryRegistryTTL(t *testing.T) {
|
||||
m := NewRegister()
|
||||
ctx := context.TODO()
|
||||
|
||||
for _, v := range testData {
|
||||
for _, service := range v {
|
||||
if err := m.Register(ctx, service, RegisterTTL(time.Millisecond)); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
time.Sleep(ttlPruneTime * 2)
|
||||
|
||||
for name := range testData {
|
||||
svcs, err := m.LookupService(ctx, name)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
for _, svc := range svcs {
|
||||
if len(svc.Nodes) > 0 {
|
||||
t.Fatalf("Service %q still has nodes registered", name)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestMemoryRegistryTTLConcurrent(t *testing.T) {
|
||||
concurrency := 1000
|
||||
waitTime := ttlPruneTime * 2
|
||||
m := NewRegister()
|
||||
ctx := context.TODO()
|
||||
for _, v := range testData {
|
||||
for _, service := range v {
|
||||
if err := m.Register(ctx, service, RegisterTTL(waitTime/2)); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if len(os.Getenv("IN_TRAVIS_CI")) == 0 {
|
||||
t.Logf("test will wait %v, then check TTL timeouts", waitTime)
|
||||
}
|
||||
|
||||
errChan := make(chan error, concurrency)
|
||||
syncChan := make(chan struct{})
|
||||
|
||||
for i := 0; i < concurrency; i++ {
|
||||
go func() {
|
||||
<-syncChan
|
||||
for name := range testData {
|
||||
svcs, err := m.LookupService(ctx, name)
|
||||
if err != nil {
|
||||
errChan <- err
|
||||
return
|
||||
}
|
||||
|
||||
for _, svc := range svcs {
|
||||
if len(svc.Nodes) > 0 {
|
||||
errChan <- fmt.Errorf("Service %q still has nodes registered", name)
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
errChan <- nil
|
||||
}()
|
||||
}
|
||||
|
||||
time.Sleep(waitTime)
|
||||
close(syncChan)
|
||||
|
||||
for i := 0; i < concurrency; i++ {
|
||||
if err := <-errChan; err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestMemoryWildcard(t *testing.T) {
|
||||
m := NewRegister()
|
||||
ctx := context.TODO()
|
||||
|
||||
testSrv := &Service{Name: "foo", Version: "1.0.0"}
|
||||
|
||||
if err := m.Register(ctx, testSrv, RegisterDomain("one")); err != nil {
|
||||
t.Fatalf("Register err: %v", err)
|
||||
}
|
||||
if err := m.Register(ctx, testSrv, RegisterDomain("two")); err != nil {
|
||||
t.Fatalf("Register err: %v", err)
|
||||
}
|
||||
|
||||
if recs, err := m.ListServices(ctx, ListDomain("one")); err != nil {
|
||||
t.Errorf("List err: %v", err)
|
||||
} else if len(recs) != 1 {
|
||||
t.Errorf("Expected 1 record, got %v", len(recs))
|
||||
}
|
||||
|
||||
if recs, err := m.ListServices(ctx, ListDomain("*")); err != nil {
|
||||
t.Errorf("List err: %v", err)
|
||||
} else if len(recs) != 2 {
|
||||
t.Errorf("Expected 2 records, got %v", len(recs))
|
||||
}
|
||||
|
||||
if recs, err := m.LookupService(ctx, testSrv.Name, LookupDomain("one")); err != nil {
|
||||
t.Errorf("Lookup err: %v", err)
|
||||
} else if len(recs) != 1 {
|
||||
t.Errorf("Expected 1 record, got %v", len(recs))
|
||||
}
|
||||
|
||||
if recs, err := m.LookupService(ctx, testSrv.Name, LookupDomain("*")); err != nil {
|
||||
t.Errorf("Lookup err: %v", err)
|
||||
} else if len(recs) != 2 {
|
||||
t.Errorf("Expected 2 records, got %v", len(recs))
|
||||
}
|
||||
}
|
||||
|
||||
func TestWatcher(t *testing.T) {
|
||||
w := &watcher{
|
||||
id: "test",
|
||||
res: make(chan *Result),
|
||||
exit: make(chan bool),
|
||||
wo: WatchOptions{
|
||||
Domain: WildcardDomain,
|
||||
},
|
||||
}
|
||||
|
||||
go func() {
|
||||
w.res <- &Result{
|
||||
Service: &Service{Name: "foo"},
|
||||
}
|
||||
}()
|
||||
|
||||
_, err := w.Next()
|
||||
if err != nil {
|
||||
t.Fatal("unexpected err", err)
|
||||
}
|
||||
|
||||
w.Stop()
|
||||
|
||||
if _, err := w.Next(); err == nil {
|
||||
t.Fatal("expected error on Next()")
|
||||
}
|
||||
}
|
@@ -1,4 +1,4 @@
|
||||
package registry
|
||||
package register
|
||||
|
||||
import (
|
||||
"context"
|
||||
@@ -6,16 +6,22 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/unistack-org/micro/v3/logger"
|
||||
"github.com/unistack-org/micro/v3/meter"
|
||||
"github.com/unistack-org/micro/v3/tracer"
|
||||
)
|
||||
|
||||
type Options struct {
|
||||
Name string
|
||||
Addrs []string
|
||||
Timeout time.Duration
|
||||
Secure bool
|
||||
TLSConfig *tls.Config
|
||||
|
||||
// Logger imp
|
||||
// Logger that will be used
|
||||
Logger logger.Logger
|
||||
// Meter that will be used
|
||||
Meter meter.Meter
|
||||
// Tracer
|
||||
Tracer tracer.Tracer
|
||||
// Other options for implementations of the interface
|
||||
// can be stored in a context
|
||||
Context context.Context
|
||||
@@ -24,6 +30,8 @@ type Options struct {
|
||||
func NewOptions(opts ...Option) Options {
|
||||
options := Options{
|
||||
Logger: logger.DefaultLogger,
|
||||
Meter: meter.DefaultMeter,
|
||||
Tracer: tracer.DefaultTracer,
|
||||
Context: context.Background(),
|
||||
}
|
||||
for _, o := range opts {
|
||||
@@ -95,14 +103,14 @@ func NewDeregisterOptions(opts ...DeregisterOption) DeregisterOptions {
|
||||
return options
|
||||
}
|
||||
|
||||
type GetOptions struct {
|
||||
type LookupOptions struct {
|
||||
Context context.Context
|
||||
// Domain to scope the request to
|
||||
Domain string
|
||||
}
|
||||
|
||||
func NewGetOptions(opts ...GetOption) GetOptions {
|
||||
options := GetOptions{
|
||||
func NewLookupOptions(opts ...LookupOption) LookupOptions {
|
||||
options := LookupOptions{
|
||||
Domain: DefaultDomain,
|
||||
Context: context.Background(),
|
||||
}
|
||||
@@ -129,7 +137,7 @@ func NewListOptions(opts ...ListOption) ListOptions {
|
||||
return options
|
||||
}
|
||||
|
||||
// Addrs is the registry addresses to use
|
||||
// Addrs is the register addresses to use
|
||||
func Addrs(addrs ...string) Option {
|
||||
return func(o *Options) {
|
||||
o.Addrs = addrs
|
||||
@@ -142,13 +150,6 @@ func Timeout(t time.Duration) Option {
|
||||
}
|
||||
}
|
||||
|
||||
// Secure communication with the registry
|
||||
func Secure(b bool) Option {
|
||||
return func(o *Options) {
|
||||
o.Secure = b
|
||||
}
|
||||
}
|
||||
|
||||
// Logger sets the logger
|
||||
func Logger(l logger.Logger) Option {
|
||||
return func(o *Options) {
|
||||
@@ -156,6 +157,20 @@ func Logger(l logger.Logger) Option {
|
||||
}
|
||||
}
|
||||
|
||||
// Meter sets the meter
|
||||
func Meter(m meter.Meter) Option {
|
||||
return func(o *Options) {
|
||||
o.Meter = m
|
||||
}
|
||||
}
|
||||
|
||||
// Tracer sets the tracer
|
||||
func Tracer(t tracer.Tracer) Option {
|
||||
return func(o *Options) {
|
||||
o.Tracer = t
|
||||
}
|
||||
}
|
||||
|
||||
// Context sets the context
|
||||
func Context(ctx context.Context) Option {
|
||||
return func(o *Options) {
|
||||
@@ -231,14 +246,14 @@ func DeregisterDomain(d string) DeregisterOption {
|
||||
}
|
||||
}
|
||||
|
||||
func GetContext(ctx context.Context) GetOption {
|
||||
return func(o *GetOptions) {
|
||||
func LookupContext(ctx context.Context) LookupOption {
|
||||
return func(o *LookupOptions) {
|
||||
o.Context = ctx
|
||||
}
|
||||
}
|
||||
|
||||
func GetDomain(d string) GetOption {
|
||||
return func(o *GetOptions) {
|
||||
func LookupDomain(d string) LookupOption {
|
||||
return func(o *LookupOptions) {
|
||||
o.Domain = d
|
||||
}
|
||||
}
|
||||
@@ -254,3 +269,10 @@ func ListDomain(d string) ListOption {
|
||||
o.Domain = d
|
||||
}
|
||||
}
|
||||
|
||||
// Name sets the name
|
||||
func Name(n string) Option {
|
||||
return func(o *Options) {
|
||||
o.Name = n
|
||||
}
|
||||
}
|
@@ -1,5 +1,5 @@
|
||||
// Package registry is an interface for service discovery
|
||||
package registry
|
||||
// Package register is an interface for service discovery
|
||||
package register
|
||||
|
||||
import (
|
||||
"context"
|
||||
@@ -16,31 +16,32 @@ const (
|
||||
)
|
||||
|
||||
var (
|
||||
// DefaultRegistry is the global default registry
|
||||
DefaultRegistry Registry = NewRegistry()
|
||||
// ErrNotFound returned when GetService is called and no services found
|
||||
// DefaultRegister is the global default register
|
||||
DefaultRegister Register = NewRegister()
|
||||
// ErrNotFound returned when LookupService is called and no services found
|
||||
ErrNotFound = errors.New("service not found")
|
||||
// ErrWatcherStopped returned when when watcher is stopped
|
||||
ErrWatcherStopped = errors.New("watcher stopped")
|
||||
)
|
||||
|
||||
// Registry provides an interface for service discovery
|
||||
// Register provides an interface for service discovery
|
||||
// and an abstraction over varying implementations
|
||||
// {consul, etcd, zookeeper, ...}
|
||||
type Registry interface {
|
||||
type Register interface {
|
||||
Name() string
|
||||
Init(...Option) error
|
||||
Options() Options
|
||||
Connect(context.Context) error
|
||||
Disconnect(context.Context) error
|
||||
Register(context.Context, *Service, ...RegisterOption) error
|
||||
Deregister(context.Context, *Service, ...DeregisterOption) error
|
||||
GetService(context.Context, string, ...GetOption) ([]*Service, error)
|
||||
LookupService(context.Context, string, ...LookupOption) ([]*Service, error)
|
||||
ListServices(context.Context, ...ListOption) ([]*Service, error)
|
||||
Watch(context.Context, ...WatchOption) (Watcher, error)
|
||||
String() string
|
||||
}
|
||||
|
||||
// Service holds service registry info
|
||||
// Service holds service register info
|
||||
type Service struct {
|
||||
Name string `json:"name"`
|
||||
Version string `json:"version"`
|
||||
@@ -49,14 +50,14 @@ type Service struct {
|
||||
Nodes []*Node `json:"nodes"`
|
||||
}
|
||||
|
||||
// Node holds node registry info
|
||||
// Node holds node register info
|
||||
type Node struct {
|
||||
Id string `json:"id"`
|
||||
Address string `json:"address"`
|
||||
Metadata metadata.Metadata `json:"metadata"`
|
||||
}
|
||||
|
||||
// Endpoint holds endpoint registry info
|
||||
// Endpoint holds endpoint register info
|
||||
type Endpoint struct {
|
||||
Name string `json:"name"`
|
||||
Request *Value `json:"request"`
|
||||
@@ -83,8 +84,8 @@ type WatchOption func(*WatchOptions)
|
||||
// DeregisterOption option is used to deregister service
|
||||
type DeregisterOption func(*DeregisterOptions)
|
||||
|
||||
// GetOption option is used to get service
|
||||
type GetOption func(*GetOptions)
|
||||
// LookupOption option is used to get service
|
||||
type LookupOption func(*LookupOptions)
|
||||
|
||||
// ListOption option is used to list services
|
||||
type ListOption func(*ListOptions)
|
@@ -1,9 +1,9 @@
|
||||
package registry
|
||||
package register
|
||||
|
||||
import "time"
|
||||
|
||||
// Watcher is an interface that returns updates
|
||||
// about services within the registry.
|
||||
// about services within the register.
|
||||
type Watcher interface {
|
||||
// Next is a blocking call
|
||||
Next() (*Result, error)
|
||||
@@ -17,7 +17,7 @@ type Result struct {
|
||||
Service *Service
|
||||
}
|
||||
|
||||
// EventType defines registry event type
|
||||
// EventType defines register event type
|
||||
type EventType int
|
||||
|
||||
const (
|
||||
@@ -43,14 +43,14 @@ func (t EventType) String() string {
|
||||
}
|
||||
}
|
||||
|
||||
// Event is registry event
|
||||
// Event is register event
|
||||
type Event struct {
|
||||
// Id is registry id
|
||||
// Id is register id
|
||||
Id string
|
||||
// Type defines type of event
|
||||
Type EventType
|
||||
// Timestamp is event timestamp
|
||||
Timestamp time.Time
|
||||
// Service is registry service
|
||||
// Service is register service
|
||||
Service *Service
|
||||
}
|
@@ -1,81 +0,0 @@
|
||||
package registry
|
||||
|
||||
import (
|
||||
"context"
|
||||
)
|
||||
|
||||
type noopRegistry struct {
|
||||
opts Options
|
||||
}
|
||||
|
||||
// Init initialize registry
|
||||
func (n *noopRegistry) Init(opts ...Option) error {
|
||||
for _, o := range opts {
|
||||
o(&n.opts)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Options returns options struct
|
||||
func (n *noopRegistry) Options() Options {
|
||||
return n.opts
|
||||
}
|
||||
|
||||
// Connect opens connection to registry
|
||||
func (n *noopRegistry) Connect(ctx context.Context) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Disconnect close connection to registry
|
||||
func (n *noopRegistry) Disconnect(ctx context.Context) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Register registers service
|
||||
func (n *noopRegistry) Register(ctx context.Context, svc *Service, opts ...RegisterOption) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Deregister deregisters service
|
||||
func (n *noopRegistry) Deregister(ctx context.Context, svc *Service, opts ...DeregisterOption) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetService returns servive info
|
||||
func (n *noopRegistry) GetService(ctx context.Context, name string, opts ...GetOption) ([]*Service, error) {
|
||||
return []*Service{}, nil
|
||||
}
|
||||
|
||||
// ListServices listing services
|
||||
func (n *noopRegistry) ListServices(ctx context.Context, opts ...ListOption) ([]*Service, error) {
|
||||
return []*Service{}, nil
|
||||
}
|
||||
|
||||
// Watch is used to watch for service changes
|
||||
func (n *noopRegistry) Watch(ctx context.Context, opts ...WatchOption) (Watcher, error) {
|
||||
return &noopWatcher{done: make(chan struct{}), opts: NewWatchOptions(opts...)}, nil
|
||||
}
|
||||
|
||||
// String returns registry string representation
|
||||
func (n *noopRegistry) String() string {
|
||||
return "noop"
|
||||
}
|
||||
|
||||
type noopWatcher struct {
|
||||
opts WatchOptions
|
||||
done chan struct{}
|
||||
}
|
||||
|
||||
func (n *noopWatcher) Next() (*Result, error) {
|
||||
<-n.done
|
||||
return nil, ErrWatcherStopped
|
||||
}
|
||||
|
||||
func (n *noopWatcher) Stop() {
|
||||
close(n.done)
|
||||
}
|
||||
|
||||
// NewRegistry returns a new noop registry
|
||||
func NewRegistry(opts ...Option) Registry {
|
||||
return &noopRegistry{opts: NewOptions(opts...)}
|
||||
}
|
@@ -1,22 +1,22 @@
|
||||
// Package registry resolves names using the micro registry
|
||||
package registry
|
||||
// Package register resolves names using the micro register
|
||||
package register
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/unistack-org/micro/v3/registry"
|
||||
"github.com/unistack-org/micro/v3/register"
|
||||
"github.com/unistack-org/micro/v3/resolver"
|
||||
)
|
||||
|
||||
// Resolver is a registry network resolver
|
||||
// Resolver is a register network resolver
|
||||
type Resolver struct {
|
||||
// Registry is the registry to use otherwise we use the defaul
|
||||
Registry registry.Registry
|
||||
// Register is the register to use otherwise we use the defaul
|
||||
Register register.Register
|
||||
}
|
||||
|
||||
// Resolve assumes ID is a domain name e.g micro.mu
|
||||
func (r *Resolver) Resolve(name string) ([]*resolver.Record, error) {
|
||||
services, err := r.Registry.GetService(context.TODO(), name)
|
||||
services, err := r.Register.LookupService(context.TODO(), name)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
@@ -5,11 +5,12 @@ import (
|
||||
|
||||
"github.com/google/uuid"
|
||||
"github.com/unistack-org/micro/v3/logger"
|
||||
"github.com/unistack-org/micro/v3/registry"
|
||||
"github.com/unistack-org/micro/v3/register"
|
||||
)
|
||||
|
||||
// Options are router options
|
||||
type Options struct {
|
||||
Name string
|
||||
// Id is router id
|
||||
Id string
|
||||
// Address is router address
|
||||
@@ -18,8 +19,8 @@ type Options struct {
|
||||
Gateway string
|
||||
// Network is network address
|
||||
Network string
|
||||
// Registry is the local registry
|
||||
Registry registry.Registry
|
||||
// Register is the local register
|
||||
Register register.Register
|
||||
// Precache routes
|
||||
Precache bool
|
||||
// Logger
|
||||
@@ -63,10 +64,10 @@ func Logger(l logger.Logger) Option {
|
||||
}
|
||||
}
|
||||
|
||||
// Registry sets the local registry
|
||||
func Registry(r registry.Registry) Option {
|
||||
// Register sets the local register
|
||||
func Register(r register.Register) Option {
|
||||
return func(o *Options) {
|
||||
o.Registry = r
|
||||
o.Register = r
|
||||
}
|
||||
}
|
||||
|
||||
@@ -77,12 +78,19 @@ func Precache() Option {
|
||||
}
|
||||
}
|
||||
|
||||
// Name of the router
|
||||
func Name(n string) Option {
|
||||
return func(o *Options) {
|
||||
o.Name = n
|
||||
}
|
||||
}
|
||||
|
||||
// NewOptions returns router default options
|
||||
func NewOptions(opts ...Option) Options {
|
||||
options := Options{
|
||||
Id: uuid.New().String(),
|
||||
Network: DefaultNetwork,
|
||||
Registry: registry.DefaultRegistry,
|
||||
Register: register.DefaultRegister,
|
||||
Logger: logger.DefaultLogger,
|
||||
Context: context.Background(),
|
||||
}
|
||||
|
@@ -18,6 +18,7 @@ var (
|
||||
|
||||
// Router is an interface for a routing control plane
|
||||
type Router interface {
|
||||
Name() string
|
||||
// Init initializes the router with options
|
||||
Init(...Option) error
|
||||
// Options returns the router options
|
||||
|
@@ -3,13 +3,13 @@ package server
|
||||
import (
|
||||
"reflect"
|
||||
|
||||
"github.com/unistack-org/micro/v3/registry"
|
||||
"github.com/unistack-org/micro/v3/register"
|
||||
)
|
||||
|
||||
type rpcHandler struct {
|
||||
name string
|
||||
handler interface{}
|
||||
endpoints []*registry.Endpoint
|
||||
endpoints []*register.Endpoint
|
||||
opts HandlerOptions
|
||||
}
|
||||
|
||||
@@ -20,10 +20,10 @@ func newRpcHandler(handler interface{}, opts ...HandlerOption) Handler {
|
||||
hdlr := reflect.ValueOf(handler)
|
||||
name := reflect.Indirect(hdlr).Type().Name()
|
||||
|
||||
var endpoints []*registry.Endpoint
|
||||
var endpoints []*register.Endpoint
|
||||
|
||||
for m := 0; m < typ.NumMethod(); m++ {
|
||||
if e := registry.ExtractEndpoint(typ.Method(m)); e != nil {
|
||||
if e := register.ExtractEndpoint(typ.Method(m)); e != nil {
|
||||
e.Name = name + "." + e.Name
|
||||
|
||||
for k, v := range options.Metadata[e.Name] {
|
||||
@@ -50,7 +50,7 @@ func (r *rpcHandler) Handler() interface{} {
|
||||
return r.handler
|
||||
}
|
||||
|
||||
func (r *rpcHandler) Endpoints() []*registry.Endpoint {
|
||||
func (r *rpcHandler) Endpoints() []*register.Endpoint {
|
||||
return r.endpoints
|
||||
}
|
||||
|
||||
|
@@ -13,7 +13,7 @@ import (
|
||||
"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/registry"
|
||||
"github.com/unistack-org/micro/v3/register"
|
||||
)
|
||||
|
||||
var (
|
||||
@@ -34,7 +34,7 @@ const (
|
||||
type noopServer struct {
|
||||
h Handler
|
||||
opts Options
|
||||
rsvc *registry.Service
|
||||
rsvc *register.Service
|
||||
handlers map[string]Handler
|
||||
subscribers map[*subscriber][]broker.Subscriber
|
||||
registered bool
|
||||
@@ -46,7 +46,17 @@ type noopServer struct {
|
||||
|
||||
// NewServer returns new noop server
|
||||
func NewServer(opts ...Option) Server {
|
||||
return &noopServer{opts: NewOptions(opts...)}
|
||||
n := &noopServer{opts: NewOptions(opts...)}
|
||||
if n.handlers == nil {
|
||||
n.handlers = make(map[string]Handler)
|
||||
}
|
||||
if n.subscribers == nil {
|
||||
n.subscribers = make(map[*subscriber][]broker.Subscriber)
|
||||
}
|
||||
if n.exit == nil {
|
||||
n.exit = make(chan chan error)
|
||||
}
|
||||
return n
|
||||
}
|
||||
|
||||
func (n *noopServer) newCodec(contentType string) (codec.Codec, error) {
|
||||
@@ -64,6 +74,10 @@ func (n *noopServer) Handle(handler Handler) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (n *noopServer) Name() string {
|
||||
return n.opts.Name
|
||||
}
|
||||
|
||||
func (n *noopServer) Subscribe(sb Subscriber) error {
|
||||
sub, ok := sb.(*subscriber)
|
||||
if !ok {
|
||||
@@ -137,10 +151,10 @@ func (n *noopServer) Register() error {
|
||||
}
|
||||
|
||||
var err error
|
||||
var service *registry.Service
|
||||
var service *register.Service
|
||||
var cacheService bool
|
||||
|
||||
service, err = NewRegistryService(n)
|
||||
service, err = NewRegisterService(n)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -168,7 +182,7 @@ func (n *noopServer) Register() error {
|
||||
return subscriberList[i].topic > subscriberList[j].topic
|
||||
})
|
||||
|
||||
endpoints := make([]*registry.Endpoint, 0, len(handlerList)+len(subscriberList))
|
||||
endpoints := make([]*register.Endpoint, 0, len(handlerList)+len(subscriberList))
|
||||
for _, h := range handlerList {
|
||||
endpoints = append(endpoints, n.handlers[h].Endpoints()...)
|
||||
}
|
||||
@@ -187,7 +201,7 @@ func (n *noopServer) Register() error {
|
||||
|
||||
if !registered {
|
||||
if config.Logger.V(logger.InfoLevel) {
|
||||
config.Logger.Infof(n.opts.Context, "registry [%s] Registering node: %s", config.Registry.String(), service.Nodes[0].Id)
|
||||
config.Logger.Infof(n.opts.Context, "register [%s] Registering node: %s", config.Register.String(), service.Nodes[0].Id)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -244,7 +258,7 @@ func (n *noopServer) Deregister() error {
|
||||
config := n.opts
|
||||
n.RUnlock()
|
||||
|
||||
service, err := NewRegistryService(n)
|
||||
service, err := NewRegisterService(n)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
@@ -12,8 +12,9 @@ import (
|
||||
"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/meter"
|
||||
"github.com/unistack-org/micro/v3/network/transport"
|
||||
"github.com/unistack-org/micro/v3/registry"
|
||||
"github.com/unistack-org/micro/v3/register"
|
||||
"github.com/unistack-org/micro/v3/tracer"
|
||||
)
|
||||
|
||||
@@ -24,10 +25,11 @@ type Option func(*Options)
|
||||
type Options struct {
|
||||
Codecs map[string]codec.Codec
|
||||
Broker broker.Broker
|
||||
Registry registry.Registry
|
||||
Register register.Register
|
||||
Tracer tracer.Tracer
|
||||
Auth auth.Auth
|
||||
Logger logger.Logger
|
||||
Meter meter.Meter
|
||||
Transport transport.Transport
|
||||
Metadata metadata.Metadata
|
||||
Name string
|
||||
@@ -78,9 +80,10 @@ func NewOptions(opts ...Option) Options {
|
||||
RegisterTTL: DefaultRegisterTTL,
|
||||
RegisterCheck: DefaultRegisterCheck,
|
||||
Logger: logger.DefaultLogger,
|
||||
Meter: meter.DefaultMeter,
|
||||
Tracer: tracer.DefaultTracer,
|
||||
Broker: broker.DefaultBroker,
|
||||
Registry: registry.DefaultRegistry,
|
||||
Register: register.DefaultRegister,
|
||||
Transport: transport.DefaultTransport,
|
||||
Address: DefaultAddress,
|
||||
Name: DefaultName,
|
||||
@@ -117,6 +120,13 @@ func Logger(l logger.Logger) Option {
|
||||
}
|
||||
}
|
||||
|
||||
// Meter sets the meter option
|
||||
func Meter(m meter.Meter) Option {
|
||||
return func(o *Options) {
|
||||
o.Meter = m
|
||||
}
|
||||
}
|
||||
|
||||
// Id unique server id
|
||||
func Id(id string) Option {
|
||||
return func(o *Options) {
|
||||
@@ -168,10 +178,10 @@ func Context(ctx context.Context) Option {
|
||||
}
|
||||
}
|
||||
|
||||
// Registry used for discovery
|
||||
func Registry(r registry.Registry) Option {
|
||||
// Register used for discovery
|
||||
func Register(r register.Register) Option {
|
||||
return func(o *Options) {
|
||||
o.Registry = r
|
||||
o.Register = r
|
||||
}
|
||||
}
|
||||
|
||||
@@ -203,7 +213,7 @@ func Metadata(md metadata.Metadata) Option {
|
||||
}
|
||||
}
|
||||
|
||||
// RegisterCheck run func before registry service
|
||||
// RegisterCheck run func before register service
|
||||
func RegisterCheck(fn func(context.Context) error) Option {
|
||||
return func(o *Options) {
|
||||
o.RegisterCheck = fn
|
||||
@@ -235,7 +245,6 @@ func TLSConfig(t *tls.Config) Option {
|
||||
|
||||
// set the transport tls
|
||||
o.Transport.Init(
|
||||
transport.Secure(true),
|
||||
transport.TLSConfig(t),
|
||||
)
|
||||
}
|
||||
|
@@ -5,23 +5,23 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/unistack-org/micro/v3/metadata"
|
||||
"github.com/unistack-org/micro/v3/registry"
|
||||
"github.com/unistack-org/micro/v3/register"
|
||||
"github.com/unistack-org/micro/v3/util/addr"
|
||||
"github.com/unistack-org/micro/v3/util/backoff"
|
||||
)
|
||||
|
||||
var (
|
||||
// DefaultRegisterFunc uses backoff to register service
|
||||
DefaultRegisterFunc = func(svc *registry.Service, config Options) error {
|
||||
DefaultRegisterFunc = func(svc *register.Service, config Options) error {
|
||||
var err error
|
||||
|
||||
opts := []registry.RegisterOption{
|
||||
registry.RegisterTTL(config.RegisterTTL),
|
||||
registry.RegisterDomain(config.Namespace),
|
||||
opts := []register.RegisterOption{
|
||||
register.RegisterTTL(config.RegisterTTL),
|
||||
register.RegisterDomain(config.Namespace),
|
||||
}
|
||||
|
||||
for i := 0; i <= config.RegisterAttempts; i++ {
|
||||
err = config.Registry.Register(config.Context, svc, opts...)
|
||||
err = config.Register.Register(config.Context, svc, opts...)
|
||||
if err == nil {
|
||||
break
|
||||
}
|
||||
@@ -32,15 +32,15 @@ var (
|
||||
return err
|
||||
}
|
||||
// DefaultDeregisterFunc uses backoff to deregister service
|
||||
DefaultDeregisterFunc = func(svc *registry.Service, config Options) error {
|
||||
DefaultDeregisterFunc = func(svc *register.Service, config Options) error {
|
||||
var err error
|
||||
|
||||
opts := []registry.DeregisterOption{
|
||||
registry.DeregisterDomain(config.Namespace),
|
||||
opts := []register.DeregisterOption{
|
||||
register.DeregisterDomain(config.Namespace),
|
||||
}
|
||||
|
||||
for i := 0; i <= config.DeregisterAttempts; i++ {
|
||||
err = config.Registry.Deregister(config.Context, svc, opts...)
|
||||
err = config.Register.Deregister(config.Context, svc, opts...)
|
||||
if err == nil {
|
||||
break
|
||||
}
|
||||
@@ -52,8 +52,8 @@ var (
|
||||
}
|
||||
)
|
||||
|
||||
// NewRegistryService returns *registry.Service from Server
|
||||
func NewRegistryService(s Server) (*registry.Service, error) {
|
||||
// NewRegisterService returns *register.Service from Server
|
||||
func NewRegisterService(s Server) (*register.Service, error) {
|
||||
opts := s.Options()
|
||||
|
||||
advt := opts.Address
|
||||
@@ -71,7 +71,7 @@ func NewRegistryService(s Server) (*registry.Service, error) {
|
||||
addr = host
|
||||
}
|
||||
|
||||
node := ®istry.Node{
|
||||
node := ®ister.Node{
|
||||
Id: opts.Name + "-" + opts.Id,
|
||||
Address: net.JoinHostPort(addr, port),
|
||||
}
|
||||
@@ -79,12 +79,12 @@ func NewRegistryService(s Server) (*registry.Service, error) {
|
||||
|
||||
node.Metadata["server"] = s.String()
|
||||
node.Metadata["broker"] = opts.Broker.String()
|
||||
node.Metadata["registry"] = opts.Registry.String()
|
||||
node.Metadata["register"] = opts.Register.String()
|
||||
|
||||
return ®istry.Service{
|
||||
return ®ister.Service{
|
||||
Name: opts.Name,
|
||||
Version: opts.Version,
|
||||
Nodes: []*registry.Node{node},
|
||||
Nodes: []*register.Node{node},
|
||||
Metadata: metadata.New(0),
|
||||
}, nil
|
||||
}
|
||||
|
@@ -8,7 +8,7 @@ import (
|
||||
"github.com/google/uuid"
|
||||
"github.com/unistack-org/micro/v3/codec"
|
||||
"github.com/unistack-org/micro/v3/metadata"
|
||||
"github.com/unistack-org/micro/v3/registry"
|
||||
"github.com/unistack-org/micro/v3/register"
|
||||
)
|
||||
|
||||
var (
|
||||
@@ -29,7 +29,7 @@ var (
|
||||
DefaultRegisterCheck = func(context.Context) error { return nil }
|
||||
// DefaultRegisterInterval holds interval for register
|
||||
DefaultRegisterInterval = time.Second * 30
|
||||
// DefaultRegisterTTL holds registry record ttl, must be multiple of DefaultRegisterInterval
|
||||
// DefaultRegisterTTL holds register record ttl, must be multiple of DefaultRegisterInterval
|
||||
DefaultRegisterTTL = time.Second * 90
|
||||
// DefaultNamespace will be used if no namespace passed
|
||||
DefaultNamespace = "micro"
|
||||
@@ -43,6 +43,8 @@ var (
|
||||
|
||||
// Server is a simple micro server abstraction
|
||||
type Server interface {
|
||||
// Name returns server name
|
||||
Name() string
|
||||
// Initialise options
|
||||
Init(...Option) error
|
||||
// Retrieve the options
|
||||
@@ -147,7 +149,7 @@ type Stream interface {
|
||||
type Handler interface {
|
||||
Name() string
|
||||
Handler() interface{}
|
||||
Endpoints() []*registry.Endpoint
|
||||
Endpoints() []*register.Endpoint
|
||||
Options() HandlerOptions
|
||||
}
|
||||
|
||||
@@ -157,6 +159,6 @@ type Handler interface {
|
||||
type Subscriber interface {
|
||||
Topic() string
|
||||
Subscriber() interface{}
|
||||
Endpoints() []*registry.Endpoint
|
||||
Endpoints() []*register.Endpoint
|
||||
Options() SubscriberOptions
|
||||
}
|
||||
|
@@ -14,7 +14,7 @@ import (
|
||||
"github.com/unistack-org/micro/v3/errors"
|
||||
"github.com/unistack-org/micro/v3/logger"
|
||||
"github.com/unistack-org/micro/v3/metadata"
|
||||
"github.com/unistack-org/micro/v3/registry"
|
||||
"github.com/unistack-org/micro/v3/register"
|
||||
)
|
||||
|
||||
const (
|
||||
@@ -39,7 +39,7 @@ type subscriber struct {
|
||||
typ reflect.Type
|
||||
subscriber interface{}
|
||||
handlers []*handler
|
||||
endpoints []*registry.Endpoint
|
||||
endpoints []*register.Endpoint
|
||||
opts SubscriberOptions
|
||||
}
|
||||
|
||||
@@ -115,7 +115,7 @@ func ValidateSubscriber(sub Subscriber) error {
|
||||
}
|
||||
|
||||
func newSubscriber(topic string, sub interface{}, opts ...SubscriberOption) Subscriber {
|
||||
var endpoints []*registry.Endpoint
|
||||
var endpoints []*register.Endpoint
|
||||
var handlers []*handler
|
||||
|
||||
options := NewSubscriberOptions(opts...)
|
||||
@@ -134,9 +134,9 @@ func newSubscriber(topic string, sub interface{}, opts ...SubscriberOption) Subs
|
||||
}
|
||||
|
||||
handlers = append(handlers, h)
|
||||
ep := ®istry.Endpoint{
|
||||
ep := ®ister.Endpoint{
|
||||
Name: "Func",
|
||||
Request: registry.ExtractSubValue(typ),
|
||||
Request: register.ExtractSubValue(typ),
|
||||
Metadata: metadata.New(2),
|
||||
}
|
||||
ep.Metadata.Set("topic", topic)
|
||||
@@ -161,9 +161,9 @@ func newSubscriber(topic string, sub interface{}, opts ...SubscriberOption) Subs
|
||||
}
|
||||
|
||||
handlers = append(handlers, h)
|
||||
ep := ®istry.Endpoint{
|
||||
ep := ®ister.Endpoint{
|
||||
Name: name + "." + method.Name,
|
||||
Request: registry.ExtractSubValue(method.Type),
|
||||
Request: register.ExtractSubValue(method.Type),
|
||||
Metadata: metadata.New(2),
|
||||
}
|
||||
ep.Metadata.Set("topic", topic)
|
||||
@@ -304,7 +304,7 @@ func (s *subscriber) Subscriber() interface{} {
|
||||
return s.subscriber
|
||||
}
|
||||
|
||||
func (s *subscriber) Endpoints() []*registry.Endpoint {
|
||||
func (s *subscriber) Endpoints() []*register.Endpoint {
|
||||
return s.endpoints
|
||||
}
|
||||
|
||||
|
278
service.go
278
service.go
@@ -1,8 +1,8 @@
|
||||
// Package micro is a pluggable framework for microservices
|
||||
package micro
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
rtime "runtime"
|
||||
"sync"
|
||||
|
||||
"github.com/unistack-org/micro/v3/auth"
|
||||
@@ -10,31 +10,86 @@ import (
|
||||
"github.com/unistack-org/micro/v3/client"
|
||||
"github.com/unistack-org/micro/v3/config"
|
||||
"github.com/unistack-org/micro/v3/logger"
|
||||
"github.com/unistack-org/micro/v3/registry"
|
||||
"github.com/unistack-org/micro/v3/meter"
|
||||
"github.com/unistack-org/micro/v3/register"
|
||||
"github.com/unistack-org/micro/v3/router"
|
||||
"github.com/unistack-org/micro/v3/server"
|
||||
"github.com/unistack-org/micro/v3/store"
|
||||
"github.com/unistack-org/micro/v3/tracer"
|
||||
)
|
||||
|
||||
// Service is an interface that wraps the lower level components.
|
||||
// Its works as container with building blocks for service.
|
||||
type Service interface {
|
||||
// The service name
|
||||
Name() string
|
||||
// Init initialises options
|
||||
Init(...Option) error
|
||||
// Options returns the current options
|
||||
Options() Options
|
||||
// Auth is for handling auth
|
||||
Auth(...string) auth.Auth
|
||||
// Logger is for logs
|
||||
Logger(...string) logger.Logger
|
||||
// Config if for config
|
||||
Config(...string) config.Config
|
||||
// Client is for calling services
|
||||
Client(...string) client.Client
|
||||
// Broker is for sending and receiving events
|
||||
Broker(...string) broker.Broker
|
||||
// Server is for handling requests and events
|
||||
Server(...string) server.Server
|
||||
// Store is for key/val store
|
||||
Store(...string) store.Store
|
||||
// Register
|
||||
Register(...string) register.Register
|
||||
// Tracer
|
||||
Tracer(...string) tracer.Tracer
|
||||
// Router
|
||||
Router(...string) router.Router
|
||||
// Meter
|
||||
Meter(...string) meter.Meter
|
||||
|
||||
// Runtime
|
||||
// Runtime(string) (runtime.Runtime, bool)
|
||||
// Profile
|
||||
// Profile(string) (profile.Profile, bool)
|
||||
// Run the service
|
||||
Run() error
|
||||
// The service implementation
|
||||
String() string
|
||||
}
|
||||
|
||||
// RegisterHandler is syntactic sugar for registering a handler
|
||||
func RegisterHandler(s server.Server, h interface{}, opts ...server.HandlerOption) error {
|
||||
return s.Handle(s.NewHandler(h, opts...))
|
||||
}
|
||||
|
||||
// RegisterSubscriber is syntactic sugar for registering a subscriber
|
||||
func RegisterSubscriber(topic string, s server.Server, h interface{}, opts ...server.SubscriberOption) error {
|
||||
return s.Subscribe(s.NewSubscriber(topic, h, opts...))
|
||||
}
|
||||
|
||||
type service struct {
|
||||
opts Options
|
||||
sync.RWMutex
|
||||
// once sync.Once
|
||||
}
|
||||
|
||||
func newService(opts ...Option) Service {
|
||||
service := &service{opts: NewOptions(opts...)}
|
||||
return service
|
||||
// NewService creates and returns a new Service based on the packages within.
|
||||
func NewService(opts ...Option) Service {
|
||||
return &service{opts: NewOptions(opts...)}
|
||||
}
|
||||
|
||||
func (s *service) Name() string {
|
||||
return s.opts.Server.Options().Name
|
||||
return s.opts.Name
|
||||
}
|
||||
|
||||
// Init initialises options. Additionally it calls cmd.Init
|
||||
// which parses command line flags. cmd.Init is only called
|
||||
// on first Init.
|
||||
func (s *service) Init(opts ...Option) error {
|
||||
var err error
|
||||
// process options
|
||||
for _, o := range opts {
|
||||
o(&s.opts)
|
||||
@@ -45,60 +100,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(s.opts.Context)); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := cfg.Load(s.opts.Context); err != nil {
|
||||
if err = cfg.Load(s.opts.Context); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
if s.opts.Logger != nil {
|
||||
if err := s.opts.Logger.Init(
|
||||
logger.WithContext(s.opts.Context),
|
||||
); err != nil {
|
||||
for _, log := range s.opts.Loggers {
|
||||
if err = log.Init(logger.WithContext(s.opts.Context)); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if s.opts.Registry != nil {
|
||||
if err := s.opts.Registry.Init(
|
||||
registry.Context(s.opts.Context),
|
||||
); err != nil {
|
||||
for _, reg := range s.opts.Registers {
|
||||
if err = reg.Init(register.Context(s.opts.Context)); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if s.opts.Broker != nil {
|
||||
if err := s.opts.Broker.Init(
|
||||
broker.Context(s.opts.Context),
|
||||
); err != nil {
|
||||
for _, brk := range s.opts.Brokers {
|
||||
if err = brk.Init(broker.Context(s.opts.Context)); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if s.opts.Store != nil {
|
||||
if err := s.opts.Store.Init(
|
||||
store.Context(s.opts.Context),
|
||||
); err != nil {
|
||||
for _, str := range s.opts.Stores {
|
||||
if err = str.Init(store.Context(s.opts.Context)); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if s.opts.Server != nil {
|
||||
if err := s.opts.Server.Init(
|
||||
server.Context(s.opts.Context),
|
||||
); err != nil {
|
||||
for _, srv := range s.opts.Servers {
|
||||
if err = srv.Init(server.Context(s.opts.Context)); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if s.opts.Client != nil {
|
||||
if err := s.opts.Client.Init(
|
||||
client.Context(s.opts.Context),
|
||||
); err != nil {
|
||||
for _, cli := range s.opts.Clients {
|
||||
if err = cli.Init(client.Context(s.opts.Context)); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
@@ -110,36 +153,93 @@ func (s *service) Options() Options {
|
||||
return s.opts
|
||||
}
|
||||
|
||||
func (s *service) Broker() broker.Broker {
|
||||
return s.opts.Broker
|
||||
func (s *service) Broker(names ...string) broker.Broker {
|
||||
idx := 0
|
||||
if len(names) == 1 {
|
||||
idx = getNameIndex(names[0], s.opts.Brokers)
|
||||
}
|
||||
return s.opts.Brokers[idx]
|
||||
|
||||
}
|
||||
|
||||
func (s *service) Client() client.Client {
|
||||
return s.opts.Client
|
||||
func (s *service) Tracer(names ...string) tracer.Tracer {
|
||||
idx := 0
|
||||
if len(names) == 1 {
|
||||
idx = getNameIndex(names[0], s.opts.Tracers)
|
||||
}
|
||||
return s.opts.Tracers[idx]
|
||||
}
|
||||
|
||||
func (s *service) Server() server.Server {
|
||||
return s.opts.Server
|
||||
func (s *service) Config(names ...string) config.Config {
|
||||
idx := 0
|
||||
if len(names) == 1 {
|
||||
idx = getNameIndex(names[0], s.opts.Configs)
|
||||
}
|
||||
return s.opts.Configs[idx]
|
||||
}
|
||||
|
||||
func (s *service) Store() store.Store {
|
||||
return s.opts.Store
|
||||
func (s *service) Client(names ...string) client.Client {
|
||||
idx := 0
|
||||
if len(names) == 1 {
|
||||
idx = getNameIndex(names[0], s.opts.Clients)
|
||||
}
|
||||
return s.opts.Clients[idx]
|
||||
}
|
||||
|
||||
func (s *service) Registry() registry.Registry {
|
||||
return s.opts.Registry
|
||||
func (s *service) Server(names ...string) server.Server {
|
||||
idx := 0
|
||||
if len(names) == 1 {
|
||||
idx = getNameIndex(names[0], s.opts.Servers)
|
||||
}
|
||||
return s.opts.Servers[idx]
|
||||
}
|
||||
|
||||
func (s *service) Logger() logger.Logger {
|
||||
return s.opts.Logger
|
||||
func (s *service) Store(names ...string) store.Store {
|
||||
idx := 0
|
||||
if len(names) == 1 {
|
||||
idx = getNameIndex(names[0], s.opts.Stores)
|
||||
}
|
||||
return s.opts.Stores[idx]
|
||||
}
|
||||
|
||||
func (s *service) Auth() auth.Auth {
|
||||
return s.opts.Auth
|
||||
func (s *service) Register(names ...string) register.Register {
|
||||
idx := 0
|
||||
if len(names) == 1 {
|
||||
idx = getNameIndex(names[0], s.opts.Registers)
|
||||
}
|
||||
return s.opts.Registers[idx]
|
||||
}
|
||||
|
||||
func (s *service) Router() router.Router {
|
||||
return s.opts.Router
|
||||
func (s *service) Logger(names ...string) logger.Logger {
|
||||
idx := 0
|
||||
if len(names) == 1 {
|
||||
idx = getNameIndex(names[0], s.opts.Loggers)
|
||||
}
|
||||
return s.opts.Loggers[idx]
|
||||
}
|
||||
|
||||
func (s *service) Auth(names ...string) auth.Auth {
|
||||
idx := 0
|
||||
if len(names) == 1 {
|
||||
idx = getNameIndex(names[0], s.opts.Auths)
|
||||
}
|
||||
return s.opts.Auths[idx]
|
||||
}
|
||||
|
||||
func (s *service) Router(names ...string) router.Router {
|
||||
idx := 0
|
||||
if len(names) == 1 {
|
||||
idx = getNameIndex(names[0], s.opts.Routers)
|
||||
}
|
||||
return s.opts.Routers[idx]
|
||||
}
|
||||
|
||||
func (s *service) Meter(names ...string) meter.Meter {
|
||||
idx := 0
|
||||
if len(names) == 1 {
|
||||
idx = getNameIndex(names[0], s.opts.Meters)
|
||||
}
|
||||
return s.opts.Meters[idx]
|
||||
}
|
||||
|
||||
func (s *service) String() string {
|
||||
@@ -153,8 +253,8 @@ func (s *service) Start() error {
|
||||
config := s.opts
|
||||
s.RUnlock()
|
||||
|
||||
if config.Logger.V(logger.InfoLevel) {
|
||||
config.Logger.Infof(s.opts.Context, "starting [service] %s", s.Name())
|
||||
if config.Loggers[0].V(logger.InfoLevel) {
|
||||
config.Loggers[0].Infof(s.opts.Context, "starting [service] %s", s.Name())
|
||||
}
|
||||
|
||||
for _, fn := range s.opts.BeforeStart {
|
||||
@@ -169,35 +269,37 @@ func (s *service) Start() error {
|
||||
continue
|
||||
}
|
||||
|
||||
if err := cfg.Load(s.opts.Context); err != nil {
|
||||
if err = cfg.Load(s.opts.Context); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if s.opts.Server == nil {
|
||||
if len(s.opts.Servers) == 0 {
|
||||
return fmt.Errorf("cant start nil server")
|
||||
}
|
||||
|
||||
if s.opts.Registry != nil {
|
||||
if err := s.opts.Registry.Connect(s.opts.Context); err != nil {
|
||||
for _, reg := range s.opts.Registers {
|
||||
if err = reg.Connect(s.opts.Context); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if s.opts.Broker != nil {
|
||||
if err := s.opts.Broker.Connect(s.opts.Context); err != nil {
|
||||
for _, brk := range s.opts.Brokers {
|
||||
if err = brk.Connect(s.opts.Context); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if s.opts.Store != nil {
|
||||
if err := s.opts.Store.Connect(s.opts.Context); err != nil {
|
||||
for _, str := range s.opts.Stores {
|
||||
if err = str.Connect(s.opts.Context); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if err = s.opts.Server.Start(); err != nil {
|
||||
return err
|
||||
for _, srv := range s.opts.Servers {
|
||||
if err = srv.Start(); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
for _, fn := range s.opts.AfterStart {
|
||||
@@ -214,8 +316,8 @@ func (s *service) Stop() error {
|
||||
config := s.opts
|
||||
s.RUnlock()
|
||||
|
||||
if config.Logger.V(logger.InfoLevel) {
|
||||
config.Logger.Infof(s.opts.Context, "stoppping [service] %s", s.Name())
|
||||
if config.Loggers[0].V(logger.InfoLevel) {
|
||||
config.Loggers[0].Infof(s.opts.Context, "stoppping [service] %s", s.Name())
|
||||
}
|
||||
|
||||
var err error
|
||||
@@ -225,8 +327,10 @@ func (s *service) Stop() error {
|
||||
}
|
||||
}
|
||||
|
||||
if err = s.opts.Server.Stop(); err != nil {
|
||||
return err
|
||||
for _, srv := range s.opts.Servers {
|
||||
if err = srv.Stop(); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
for _, fn := range s.opts.AfterStop {
|
||||
@@ -235,20 +339,20 @@ func (s *service) Stop() error {
|
||||
}
|
||||
}
|
||||
|
||||
if s.opts.Registry != nil {
|
||||
if err := s.opts.Registry.Disconnect(s.opts.Context); err != nil {
|
||||
for _, reg := range s.opts.Registers {
|
||||
if err = reg.Disconnect(s.opts.Context); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if s.opts.Broker != nil {
|
||||
if err := s.opts.Broker.Disconnect(s.opts.Context); err != nil {
|
||||
for _, brk := range s.opts.Brokers {
|
||||
if err = brk.Disconnect(s.opts.Context); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if s.opts.Store != nil {
|
||||
if err := s.opts.Store.Disconnect(s.opts.Context); err != nil {
|
||||
for _, str := range s.opts.Stores {
|
||||
if err = str.Disconnect(s.opts.Context); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
@@ -258,18 +362,19 @@ func (s *service) Stop() error {
|
||||
|
||||
func (s *service) Run() error {
|
||||
// start the profiler
|
||||
if s.opts.Profile != nil {
|
||||
// to view mutex contention
|
||||
rtime.SetMutexProfileFraction(5)
|
||||
// to view blocking profile
|
||||
rtime.SetBlockProfileRate(1)
|
||||
/*
|
||||
if s.opts.Profile != nil {
|
||||
// to view mutex contention
|
||||
rtime.SetMutexProfileFraction(5)
|
||||
// to view blocking profile
|
||||
rtime.SetBlockProfileRate(1)
|
||||
|
||||
if err := s.opts.Profile.Start(); err != nil {
|
||||
return err
|
||||
if err := s.opts.Profile.Start(); err != nil {
|
||||
return err
|
||||
}
|
||||
defer s.opts.Profile.Stop()
|
||||
}
|
||||
defer s.opts.Profile.Stop()
|
||||
}
|
||||
|
||||
*/
|
||||
if err := s.Start(); err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -279,3 +384,16 @@ func (s *service) Run() error {
|
||||
|
||||
return s.Stop()
|
||||
}
|
||||
|
||||
type nameIface interface {
|
||||
Name() string
|
||||
}
|
||||
|
||||
func getNameIndex(n string, ifaces ...interface{}) int {
|
||||
for idx, iface := range ifaces {
|
||||
if ifc, ok := iface.(nameIface); ok && ifc.Name() == n {
|
||||
return idx
|
||||
}
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
@@ -32,3 +32,53 @@ func SetOption(k, v interface{}) Option {
|
||||
o.Context = context.WithValue(o.Context, k, v)
|
||||
}
|
||||
}
|
||||
|
||||
// SetReadOption returns a function to setup a context with given value
|
||||
func SetReadOption(k, v interface{}) ReadOption {
|
||||
return func(o *ReadOptions) {
|
||||
if o.Context == nil {
|
||||
o.Context = context.Background()
|
||||
}
|
||||
o.Context = context.WithValue(o.Context, k, v)
|
||||
}
|
||||
}
|
||||
|
||||
// SetWriteOption returns a function to setup a context with given value
|
||||
func SetWriteOption(k, v interface{}) WriteOption {
|
||||
return func(o *WriteOptions) {
|
||||
if o.Context == nil {
|
||||
o.Context = context.Background()
|
||||
}
|
||||
o.Context = context.WithValue(o.Context, k, v)
|
||||
}
|
||||
}
|
||||
|
||||
// SetListOption returns a function to setup a context with given value
|
||||
func SetListOption(k, v interface{}) ListOption {
|
||||
return func(o *ListOptions) {
|
||||
if o.Context == nil {
|
||||
o.Context = context.Background()
|
||||
}
|
||||
o.Context = context.WithValue(o.Context, k, v)
|
||||
}
|
||||
}
|
||||
|
||||
// SetDeleteOption returns a function to setup a context with given value
|
||||
func SetDeleteOption(k, v interface{}) DeleteOption {
|
||||
return func(o *DeleteOptions) {
|
||||
if o.Context == nil {
|
||||
o.Context = context.Background()
|
||||
}
|
||||
o.Context = context.WithValue(o.Context, k, v)
|
||||
}
|
||||
}
|
||||
|
||||
// SetExistsOption returns a function to setup a context with given value
|
||||
func SetExistsOption(k, v interface{}) ExistsOption {
|
||||
return func(o *ExistsOptions) {
|
||||
if o.Context == nil {
|
||||
o.Context = context.Background()
|
||||
}
|
||||
o.Context = context.WithValue(o.Context, k, v)
|
||||
}
|
||||
}
|
||||
|
199
store/memory.go
Normal file
199
store/memory.go
Normal file
@@ -0,0 +1,199 @@
|
||||
package store
|
||||
|
||||
import (
|
||||
"context"
|
||||
"path/filepath"
|
||||
"sort"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/patrickmn/go-cache"
|
||||
)
|
||||
|
||||
// NewStore returns a memory store
|
||||
func NewStore(opts ...Option) Store {
|
||||
return &memoryStore{
|
||||
opts: NewOptions(opts...),
|
||||
store: cache.New(cache.NoExpiration, 5*time.Minute),
|
||||
}
|
||||
}
|
||||
|
||||
func (m *memoryStore) Connect(ctx context.Context) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *memoryStore) Disconnect(ctx context.Context) error {
|
||||
m.store.Flush()
|
||||
return nil
|
||||
}
|
||||
|
||||
type memoryStore struct {
|
||||
opts Options
|
||||
store *cache.Cache
|
||||
}
|
||||
|
||||
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)
|
||||
|
||||
_, found := m.store.Get(key)
|
||||
if !found {
|
||||
return ErrNotFound
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *memoryStore) get(prefix, key string, val interface{}) error {
|
||||
key = m.key(prefix, key)
|
||||
|
||||
r, found := m.store.Get(key)
|
||||
if !found {
|
||||
return ErrNotFound
|
||||
}
|
||||
|
||||
buf, ok := r.([]byte)
|
||||
if !ok {
|
||||
return ErrNotFound
|
||||
}
|
||||
|
||||
if err := m.opts.Codec.Unmarshal(buf, val); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *memoryStore) delete(prefix, key string) {
|
||||
key = m.key(prefix, key)
|
||||
m.store.Delete(key)
|
||||
}
|
||||
|
||||
func (m *memoryStore) list(prefix string, limit, offset uint) []string {
|
||||
allItems := m.store.Items()
|
||||
allKeys := make([]string, len(allItems))
|
||||
i := 0
|
||||
|
||||
for k := range allItems {
|
||||
if !strings.HasPrefix(k, prefix+"/") {
|
||||
continue
|
||||
}
|
||||
allKeys[i] = strings.TrimPrefix(k, prefix+"/")
|
||||
i++
|
||||
}
|
||||
|
||||
if limit != 0 || offset != 0 {
|
||||
sort.Slice(allKeys, func(i, j int) bool { return allKeys[i] < allKeys[j] })
|
||||
sort.Slice(allKeys, func(i, j int) bool { return allKeys[i] < allKeys[j] })
|
||||
end := len(allKeys)
|
||||
if limit > 0 {
|
||||
calcLimit := int(offset + limit)
|
||||
if calcLimit < end {
|
||||
end = calcLimit
|
||||
}
|
||||
}
|
||||
|
||||
if int(offset) >= end {
|
||||
return nil
|
||||
}
|
||||
return allKeys[offset:end]
|
||||
}
|
||||
|
||||
return allKeys
|
||||
}
|
||||
|
||||
func (m *memoryStore) Init(opts ...Option) error {
|
||||
for _, o := range opts {
|
||||
o(&m.opts)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *memoryStore) String() string {
|
||||
return "memory"
|
||||
}
|
||||
|
||||
func (m *memoryStore) Name() string {
|
||||
return m.opts.Name
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
func (m *memoryStore) Write(ctx context.Context, key string, val interface{}, opts ...WriteOption) error {
|
||||
writeOpts := NewWriteOptions(opts...)
|
||||
|
||||
prefix := m.prefix(writeOpts.Database, writeOpts.Table)
|
||||
|
||||
key = m.key(prefix, key)
|
||||
|
||||
buf, err := m.opts.Codec.Marshal(val)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
m.store.Set(key, buf, writeOpts.TTL)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *memoryStore) Delete(ctx context.Context, key string, opts ...DeleteOption) error {
|
||||
deleteOptions := NewDeleteOptions(opts...)
|
||||
|
||||
prefix := m.prefix(deleteOptions.Database, deleteOptions.Table)
|
||||
m.delete(prefix, key)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *memoryStore) Options() Options {
|
||||
return m.opts
|
||||
}
|
||||
|
||||
func (m *memoryStore) List(ctx context.Context, opts ...ListOption) ([]string, error) {
|
||||
listOptions := NewListOptions(opts...)
|
||||
|
||||
prefix := m.prefix(listOptions.Database, listOptions.Table)
|
||||
keys := m.list(prefix, listOptions.Limit, listOptions.Offset)
|
||||
|
||||
if len(listOptions.Prefix) > 0 {
|
||||
var prefixKeys []string
|
||||
for _, k := range keys {
|
||||
if strings.HasPrefix(k, listOptions.Prefix) {
|
||||
prefixKeys = append(prefixKeys, k)
|
||||
}
|
||||
}
|
||||
keys = prefixKeys
|
||||
}
|
||||
|
||||
if len(listOptions.Suffix) > 0 {
|
||||
var suffixKeys []string
|
||||
for _, k := range keys {
|
||||
if strings.HasSuffix(k, listOptions.Suffix) {
|
||||
suffixKeys = append(suffixKeys, k)
|
||||
}
|
||||
}
|
||||
keys = suffixKeys
|
||||
}
|
||||
|
||||
return keys, nil
|
||||
}
|
67
store/memory_test.go
Normal file
67
store/memory_test.go
Normal file
@@ -0,0 +1,67 @@
|
||||
package store_test
|
||||
|
||||
import (
|
||||
"context"
|
||||
"os"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/unistack-org/micro/v3/store"
|
||||
)
|
||||
|
||||
func TestMemoryReInit(t *testing.T) {
|
||||
s := store.NewStore(store.Table("aaa"))
|
||||
s.Init(store.Table(""))
|
||||
if len(s.Options().Table) > 0 {
|
||||
t.Error("Init didn't reinitialise the store")
|
||||
}
|
||||
}
|
||||
|
||||
func TestMemoryBasic(t *testing.T) {
|
||||
s := store.NewStore()
|
||||
s.Init()
|
||||
basictest(s, t)
|
||||
}
|
||||
|
||||
func TestMemoryPrefix(t *testing.T) {
|
||||
s := store.NewStore()
|
||||
s.Init(store.Table("some-prefix"))
|
||||
basictest(s, t)
|
||||
}
|
||||
|
||||
func TestMemoryNamespace(t *testing.T) {
|
||||
s := store.NewStore()
|
||||
s.Init(store.Database("some-namespace"))
|
||||
basictest(s, t)
|
||||
}
|
||||
|
||||
func TestMemoryNamespacePrefix(t *testing.T) {
|
||||
s := store.NewStore()
|
||||
s.Init(store.Table("some-prefix"), store.Database("some-namespace"))
|
||||
basictest(s, t)
|
||||
}
|
||||
|
||||
func basictest(s store.Store, t *testing.T) {
|
||||
ctx := context.Background()
|
||||
if len(os.Getenv("IN_TRAVIS_CI")) == 0 {
|
||||
t.Logf("Testing store %s, with options %#+v\n", s.String(), s.Options())
|
||||
}
|
||||
// Read and Write an expiring Record
|
||||
if err := s.Write(ctx, "Hello", "World", store.WriteTTL(time.Millisecond*100)); err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
var val []byte
|
||||
if err := s.Read(ctx, "Hello", &val); err != nil {
|
||||
t.Error(err)
|
||||
} else {
|
||||
if string(val) != "World" {
|
||||
t.Errorf("Expected %s, got %s", "World", val)
|
||||
}
|
||||
}
|
||||
time.Sleep(time.Millisecond * 200)
|
||||
if err := s.Read(ctx, "Hello", &val); err != store.ErrNotFound {
|
||||
t.Errorf("Expected %# v, got %# v", store.ErrNotFound, err)
|
||||
}
|
||||
|
||||
s.Disconnect(ctx) // reset the store
|
||||
}
|
@@ -1,64 +0,0 @@
|
||||
package store
|
||||
|
||||
import "context"
|
||||
|
||||
type noopStore struct {
|
||||
opts Options
|
||||
}
|
||||
|
||||
func NewStore(opts ...Option) Store {
|
||||
return &noopStore{opts: NewOptions(opts...)}
|
||||
}
|
||||
|
||||
// Init initialize store
|
||||
func (n *noopStore) Init(opts ...Option) error {
|
||||
for _, o := range opts {
|
||||
o(&n.opts)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Options returns Options struct
|
||||
func (n *noopStore) Options() Options {
|
||||
return n.opts
|
||||
}
|
||||
|
||||
// String returns string representation
|
||||
func (n *noopStore) String() string {
|
||||
return "noop"
|
||||
}
|
||||
|
||||
// Read reads store value by key
|
||||
func (n *noopStore) Exists(ctx context.Context, key string) error {
|
||||
return ErrNotFound
|
||||
}
|
||||
|
||||
// Read reads store value by key
|
||||
func (n *noopStore) Read(ctx context.Context, key string, val interface{}, opts ...ReadOption) error {
|
||||
return ErrNotFound
|
||||
}
|
||||
|
||||
// Write writes store record
|
||||
func (n *noopStore) Write(ctx context.Context, key string, val interface{}, opts ...WriteOption) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Delete removes store value by key
|
||||
func (n *noopStore) Delete(ctx context.Context, key string, opts ...DeleteOption) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// List lists store
|
||||
func (n *noopStore) List(ctx context.Context, opts ...ListOption) ([]string, error) {
|
||||
return []string{}, nil
|
||||
}
|
||||
|
||||
// Connect connects to store
|
||||
func (n *noopStore) Connect(ctx context.Context) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Disconnect disconnects from store
|
||||
func (n *noopStore) Disconnect(ctx context.Context) error {
|
||||
return nil
|
||||
}
|
@@ -2,15 +2,19 @@ package store
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/tls"
|
||||
"time"
|
||||
|
||||
"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/meter"
|
||||
"github.com/unistack-org/micro/v3/tracer"
|
||||
)
|
||||
|
||||
// Options contains configuration for the Store
|
||||
type Options struct {
|
||||
Name string
|
||||
// Nodes 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.
|
||||
@@ -23,6 +27,13 @@ type Options struct {
|
||||
Codec codec.Codec
|
||||
// Logger the logger
|
||||
Logger logger.Logger
|
||||
// Meter the meter
|
||||
Meter meter.Meter
|
||||
// Tracer the tacer
|
||||
Tracer tracer.Tracer
|
||||
// TLSConfig specifies tls.Config for secure
|
||||
TLSConfig *tls.Config
|
||||
|
||||
// Context should contain all implementation specific options, using context.WithValue.
|
||||
Context context.Context
|
||||
}
|
||||
@@ -33,6 +44,8 @@ func NewOptions(opts ...Option) Options {
|
||||
Logger: logger.DefaultLogger,
|
||||
Context: context.Background(),
|
||||
Codec: codec.DefaultCodec,
|
||||
Tracer: tracer.DefaultTracer,
|
||||
Meter: meter.DefaultMeter,
|
||||
}
|
||||
for _, o := range opts {
|
||||
o(&options)
|
||||
@@ -43,6 +56,13 @@ func NewOptions(opts ...Option) Options {
|
||||
// Option sets values in Options
|
||||
type Option func(o *Options)
|
||||
|
||||
// TLSConfig specifies a *tls.Config
|
||||
func TLSConfig(t *tls.Config) Option {
|
||||
return func(o *Options) {
|
||||
o.TLSConfig = t
|
||||
}
|
||||
}
|
||||
|
||||
// Context pass context to store
|
||||
func Context(ctx context.Context) Option {
|
||||
return func(o *Options) {
|
||||
@@ -64,6 +84,27 @@ func Logger(l logger.Logger) Option {
|
||||
}
|
||||
}
|
||||
|
||||
// Meter sets the meter
|
||||
func Meter(m meter.Meter) Option {
|
||||
return func(o *Options) {
|
||||
o.Meter = m
|
||||
}
|
||||
}
|
||||
|
||||
// Name the name
|
||||
func Name(n string) Option {
|
||||
return func(o *Options) {
|
||||
o.Name = n
|
||||
}
|
||||
}
|
||||
|
||||
// Tracer sets the tracer
|
||||
func Tracer(t tracer.Tracer) Option {
|
||||
return func(o *Options) {
|
||||
o.Tracer = t
|
||||
}
|
||||
}
|
||||
|
||||
// Nodes 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.
|
||||
@@ -98,8 +139,10 @@ func NewReadOptions(opts ...ReadOption) ReadOptions {
|
||||
|
||||
// ReadOptions configures an individual Read operation
|
||||
type ReadOptions struct {
|
||||
Database string
|
||||
Table string
|
||||
Database string
|
||||
Table string
|
||||
Namespace string
|
||||
Context context.Context
|
||||
}
|
||||
|
||||
// ReadOption sets values in ReadOptions
|
||||
@@ -124,10 +167,12 @@ func NewWriteOptions(opts ...WriteOption) WriteOptions {
|
||||
|
||||
// WriteOptions configures an individual Write operation
|
||||
type WriteOptions struct {
|
||||
Database string
|
||||
Table string
|
||||
TTL time.Duration
|
||||
Metadata metadata.Metadata
|
||||
Database string
|
||||
Table string
|
||||
TTL time.Duration
|
||||
Metadata metadata.Metadata
|
||||
Namespace string
|
||||
Context context.Context
|
||||
}
|
||||
|
||||
// WriteOption sets values in WriteOptions
|
||||
@@ -166,7 +211,10 @@ func NewDeleteOptions(opts ...DeleteOption) DeleteOptions {
|
||||
|
||||
// DeleteOptions configures an individual Delete operation
|
||||
type DeleteOptions struct {
|
||||
Database, Table string
|
||||
Database string
|
||||
Table string
|
||||
Namespace string
|
||||
Context context.Context
|
||||
}
|
||||
|
||||
// DeleteOption sets values in DeleteOptions
|
||||
@@ -200,7 +248,9 @@ type ListOptions struct {
|
||||
// Limit limits the number of returned keys
|
||||
Limit uint
|
||||
// Offset when combined with Limit supports pagination
|
||||
Offset uint
|
||||
Offset uint
|
||||
Namespace string
|
||||
Context context.Context
|
||||
}
|
||||
|
||||
// ListOption sets values in ListOptions
|
||||
@@ -241,3 +291,20 @@ func ListOffset(o uint) ListOption {
|
||||
l.Offset = o
|
||||
}
|
||||
}
|
||||
|
||||
type ExistsOption func(*ExistsOptions)
|
||||
|
||||
type ExistsOptions struct {
|
||||
Namespace string
|
||||
Context context.Context
|
||||
}
|
||||
|
||||
func NewExistsOptions(opts ...ExistsOption) ExistsOptions {
|
||||
options := ExistsOptions{
|
||||
Context: context.Background(),
|
||||
}
|
||||
for _, o := range opts {
|
||||
o(&options)
|
||||
}
|
||||
return options
|
||||
}
|
||||
|
@@ -12,12 +12,15 @@ import (
|
||||
var (
|
||||
// ErrNotFound is returned when a key doesn't exist
|
||||
ErrNotFound = errors.New("not found")
|
||||
// ErrInvalidKey is returned when a key has empty or have invalid format
|
||||
ErrInvalidKey = errors.New("invalid key")
|
||||
// DefaultStore is the global default store
|
||||
DefaultStore Store = NewStore()
|
||||
)
|
||||
|
||||
// Store is a data storage interface
|
||||
type Store interface {
|
||||
Name() string
|
||||
// Init initialises the store
|
||||
Init(opts ...Option) error
|
||||
// Connect is used when store needs to be connected
|
||||
@@ -25,7 +28,7 @@ type Store interface {
|
||||
// Options allows you to view the current options.
|
||||
Options() Options
|
||||
// Exists check that key exists in store
|
||||
Exists(ctx context.Context, key string) error
|
||||
Exists(ctx context.Context, key string, opts ...ExistsOption) error
|
||||
// Read reads a single key name to provided value with optional ReadOptions
|
||||
Read(ctx context.Context, key string, val interface{}, opts ...ReadOption) error
|
||||
// Write writes a value to key name to the store with optional WriteOption
|
||||
|
199
sync/memory.go
Normal file
199
sync/memory.go
Normal file
@@ -0,0 +1,199 @@
|
||||
package sync
|
||||
|
||||
import (
|
||||
gosync "sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
type memorySync struct {
|
||||
options Options
|
||||
|
||||
mtx gosync.RWMutex
|
||||
locks map[string]*memoryLock
|
||||
}
|
||||
|
||||
type memoryLock struct {
|
||||
id string
|
||||
time time.Time
|
||||
ttl time.Duration
|
||||
release chan bool
|
||||
}
|
||||
|
||||
type memoryLeader struct {
|
||||
opts LeaderOptions
|
||||
id string
|
||||
resign func(id string) error
|
||||
status chan bool
|
||||
}
|
||||
|
||||
func (m *memoryLeader) Resign() error {
|
||||
return m.resign(m.id)
|
||||
}
|
||||
|
||||
func (m *memoryLeader) Status() chan bool {
|
||||
return m.status
|
||||
}
|
||||
|
||||
func (m *memorySync) Leader(id string, opts ...LeaderOption) (Leader, error) {
|
||||
var once gosync.Once
|
||||
var options LeaderOptions
|
||||
for _, o := range opts {
|
||||
o(&options)
|
||||
}
|
||||
|
||||
// acquire a lock for the id
|
||||
if err := m.Lock(id); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// return the leader
|
||||
return &memoryLeader{
|
||||
opts: options,
|
||||
id: id,
|
||||
resign: func(id string) error {
|
||||
once.Do(func() {
|
||||
m.Unlock(id)
|
||||
})
|
||||
return nil
|
||||
},
|
||||
// TODO: signal when Unlock is called
|
||||
status: make(chan bool, 1),
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (m *memorySync) Init(opts ...Option) error {
|
||||
for _, o := range opts {
|
||||
o(&m.options)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *memorySync) Options() Options {
|
||||
return m.options
|
||||
}
|
||||
|
||||
func (m *memorySync) Lock(id string, opts ...LockOption) error {
|
||||
// lock our access
|
||||
m.mtx.Lock()
|
||||
|
||||
var options LockOptions
|
||||
for _, o := range opts {
|
||||
o(&options)
|
||||
}
|
||||
|
||||
lk, ok := m.locks[id]
|
||||
if !ok {
|
||||
m.locks[id] = &memoryLock{
|
||||
id: id,
|
||||
time: time.Now(),
|
||||
ttl: options.TTL,
|
||||
release: make(chan bool),
|
||||
}
|
||||
// unlock
|
||||
m.mtx.Unlock()
|
||||
return nil
|
||||
}
|
||||
|
||||
m.mtx.Unlock()
|
||||
|
||||
// set wait time
|
||||
var wait <-chan time.Time
|
||||
var ttl <-chan time.Time
|
||||
|
||||
// decide if we should wait
|
||||
if options.Wait > time.Duration(0) {
|
||||
wait = time.After(options.Wait)
|
||||
}
|
||||
|
||||
// check the ttl of the lock
|
||||
if lk.ttl > time.Duration(0) {
|
||||
// time lived for the lock
|
||||
live := time.Since(lk.time)
|
||||
|
||||
// set a timer for the leftover ttl
|
||||
if live > lk.ttl {
|
||||
// release the lock if it expired
|
||||
_ = m.Unlock(id)
|
||||
} else {
|
||||
ttl = time.After(live)
|
||||
}
|
||||
}
|
||||
|
||||
lockLoop:
|
||||
for {
|
||||
// wait for the lock to be released
|
||||
select {
|
||||
case <-lk.release:
|
||||
m.mtx.Lock()
|
||||
|
||||
// someone locked before us
|
||||
lk, ok = m.locks[id]
|
||||
if ok {
|
||||
m.mtx.Unlock()
|
||||
continue
|
||||
}
|
||||
|
||||
// got chance to lock
|
||||
m.locks[id] = &memoryLock{
|
||||
id: id,
|
||||
time: time.Now(),
|
||||
ttl: options.TTL,
|
||||
release: make(chan bool),
|
||||
}
|
||||
|
||||
m.mtx.Unlock()
|
||||
|
||||
break lockLoop
|
||||
case <-ttl:
|
||||
// ttl exceeded
|
||||
_ = m.Unlock(id)
|
||||
// TODO: check the ttl again above
|
||||
ttl = nil
|
||||
// try acquire
|
||||
continue
|
||||
case <-wait:
|
||||
return ErrLockTimeout
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *memorySync) Unlock(id string) error {
|
||||
m.mtx.Lock()
|
||||
defer m.mtx.Unlock()
|
||||
|
||||
lk, ok := m.locks[id]
|
||||
// no lock exists
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
|
||||
// delete the lock
|
||||
delete(m.locks, id)
|
||||
|
||||
select {
|
||||
case <-lk.release:
|
||||
return nil
|
||||
default:
|
||||
close(lk.release)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *memorySync) String() string {
|
||||
return "memory"
|
||||
}
|
||||
|
||||
func NewSync(opts ...Option) Sync {
|
||||
var options Options
|
||||
for _, o := range opts {
|
||||
o(&options)
|
||||
}
|
||||
|
||||
return &memorySync{
|
||||
options: options,
|
||||
locks: make(map[string]*memoryLock),
|
||||
}
|
||||
}
|
@@ -4,16 +4,34 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/unistack-org/micro/v3/logger"
|
||||
"github.com/unistack-org/micro/v3/meter"
|
||||
"github.com/unistack-org/micro/v3/tracer"
|
||||
)
|
||||
|
||||
type Options struct {
|
||||
Nodes []string
|
||||
Prefix string
|
||||
Logger logger.Logger
|
||||
Tracer tracer.Tracer
|
||||
Meter meter.Meter
|
||||
}
|
||||
|
||||
type Option func(o *Options)
|
||||
|
||||
func NewOptions(opts ...Option) Options {
|
||||
options := Options{
|
||||
Logger: logger.DefaultLogger,
|
||||
Meter: meter.DefaultMeter,
|
||||
Tracer: tracer.DefaultTracer,
|
||||
}
|
||||
|
||||
for _, o := range opts {
|
||||
o(&options)
|
||||
}
|
||||
|
||||
return options
|
||||
}
|
||||
|
||||
type LeaderOptions struct{}
|
||||
|
||||
type LeaderOption func(o *LeaderOptions)
|
||||
@@ -32,6 +50,20 @@ func Logger(l logger.Logger) Option {
|
||||
}
|
||||
}
|
||||
|
||||
// Meter sets the logger
|
||||
func Meter(m meter.Meter) Option {
|
||||
return func(o *Options) {
|
||||
o.Meter = m
|
||||
}
|
||||
}
|
||||
|
||||
// Tracer sets the tracer
|
||||
func Tracer(t tracer.Tracer) Option {
|
||||
return func(o *Options) {
|
||||
o.Tracer = t
|
||||
}
|
||||
}
|
||||
|
||||
// Nodes sets the addresses to use
|
||||
func Nodes(a ...string) Option {
|
||||
return func(o *Options) {
|
||||
|
@@ -14,11 +14,12 @@ const (
|
||||
|
||||
// FromContext returns a span from context
|
||||
func FromContext(ctx context.Context) (traceID string, parentSpanID string, isFound bool) {
|
||||
if ctx == nil {
|
||||
md, ok := metadata.FromIncomingContext(ctx)
|
||||
if !ok {
|
||||
return "", "", false
|
||||
}
|
||||
traceID, traceOk := metadata.Get(ctx, traceIDKey)
|
||||
microID, microOk := metadata.Get(ctx, "Micro-Id")
|
||||
traceID, traceOk := md.Get(traceIDKey)
|
||||
microID, microOk := md.Get("Micro-Id")
|
||||
if !traceOk && !microOk {
|
||||
isFound = false
|
||||
return
|
||||
@@ -26,17 +27,17 @@ func FromContext(ctx context.Context) (traceID string, parentSpanID string, isFo
|
||||
if !traceOk {
|
||||
traceID = microID
|
||||
}
|
||||
parentSpanID, ok := metadata.Get(ctx, spanIDKey)
|
||||
parentSpanID, ok = md.Get(spanIDKey)
|
||||
return traceID, parentSpanID, ok
|
||||
}
|
||||
|
||||
// NewContext saves the trace and span ids in the context
|
||||
func NewContext(ctx context.Context, traceID, parentSpanID string) context.Context {
|
||||
if ctx == nil {
|
||||
ctx = context.Background()
|
||||
md, ok := metadata.FromContext(ctx)
|
||||
if !ok {
|
||||
md = metadata.New(2)
|
||||
}
|
||||
md := metadata.New(2)
|
||||
md.Set(traceIDKey, traceID)
|
||||
md.Set(spanIDKey, parentSpanID)
|
||||
return metadata.MergeContext(ctx, md, true)
|
||||
return metadata.NewContext(ctx, md)
|
||||
}
|
||||
|
98
tracer/memory.go
Normal file
98
tracer/memory.go
Normal file
@@ -0,0 +1,98 @@
|
||||
package tracer
|
||||
|
||||
import (
|
||||
"context"
|
||||
"time"
|
||||
|
||||
"github.com/google/uuid"
|
||||
"github.com/unistack-org/micro/v3/util/ring"
|
||||
)
|
||||
|
||||
type tracer struct {
|
||||
opts Options
|
||||
// ring buffer of traces
|
||||
buffer *ring.Buffer
|
||||
}
|
||||
|
||||
func (t *tracer) Read(opts ...ReadOption) ([]*Span, error) {
|
||||
var options ReadOptions
|
||||
for _, o := range opts {
|
||||
o(&options)
|
||||
}
|
||||
|
||||
sp := t.buffer.Get(t.buffer.Size())
|
||||
|
||||
spans := make([]*Span, 0, len(sp))
|
||||
|
||||
for _, span := range sp {
|
||||
val := span.Value.(*Span)
|
||||
// skip if trace id is specified and doesn't match
|
||||
if len(options.Trace) > 0 && val.Trace != options.Trace {
|
||||
continue
|
||||
}
|
||||
spans = append(spans, val)
|
||||
}
|
||||
|
||||
return spans, nil
|
||||
}
|
||||
|
||||
func (t *tracer) Start(ctx context.Context, name string) (context.Context, *Span) {
|
||||
span := &Span{
|
||||
Name: name,
|
||||
Trace: uuid.New().String(),
|
||||
Id: uuid.New().String(),
|
||||
Started: time.Now(),
|
||||
Metadata: make(map[string]string),
|
||||
}
|
||||
|
||||
// return span if no context
|
||||
if ctx == nil {
|
||||
return NewContext(context.Background(), span.Trace, span.Id), span
|
||||
}
|
||||
traceID, parentSpanID, ok := FromContext(ctx)
|
||||
// If the trace can not be found in the header,
|
||||
// that means this is where the trace is created.
|
||||
if !ok {
|
||||
return NewContext(ctx, span.Trace, span.Id), span
|
||||
}
|
||||
|
||||
// set trace id
|
||||
span.Trace = traceID
|
||||
// set parent
|
||||
span.Parent = parentSpanID
|
||||
|
||||
// return the span
|
||||
return NewContext(ctx, span.Trace, span.Id), span
|
||||
}
|
||||
|
||||
func (t *tracer) Finish(s *Span) error {
|
||||
// set finished time
|
||||
s.Duration = time.Since(s.Started)
|
||||
// save the span
|
||||
t.buffer.Put(s)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (t *tracer) Init(opts ...Option) error {
|
||||
for _, o := range opts {
|
||||
o(&t.opts)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (t *tracer) Lookup(ctx context.Context) (*Span, error) {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func (t *tracer) Name() string {
|
||||
return t.opts.Name
|
||||
}
|
||||
|
||||
func NewTracer(opts ...Option) Tracer {
|
||||
return &tracer{
|
||||
opts: NewOptions(opts...),
|
||||
// the last 256 requests
|
||||
buffer: ring.New(256),
|
||||
}
|
||||
}
|
@@ -1,35 +0,0 @@
|
||||
package tracer
|
||||
|
||||
import "context"
|
||||
|
||||
type noopTracer struct {
|
||||
opts Options
|
||||
}
|
||||
|
||||
// Init initilize tracer
|
||||
func (n *noopTracer) Init(opts ...Option) error {
|
||||
for _, o := range opts {
|
||||
o(&n.opts)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Start starts new span
|
||||
func (n *noopTracer) Start(ctx context.Context, name string) (context.Context, *Span) {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
// Finish finishes span
|
||||
func (n *noopTracer) Finish(*Span) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Read reads span
|
||||
func (n *noopTracer) Read(...ReadOption) ([]*Span, error) {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
// NewTracer returns new noop tracer
|
||||
func NewTracer(opts ...Option) Tracer {
|
||||
return &noopTracer{opts: NewOptions(opts...)}
|
||||
}
|
@@ -9,6 +9,7 @@ var (
|
||||
|
||||
// Options struct
|
||||
type Options struct {
|
||||
Name string
|
||||
// Logger is the logger for messages
|
||||
Logger logger.Logger
|
||||
// Size is the size of ring buffer
|
||||
@@ -52,3 +53,10 @@ func NewOptions(opts ...Option) Options {
|
||||
}
|
||||
return options
|
||||
}
|
||||
|
||||
// Name sets the name
|
||||
func Name(n string) Option {
|
||||
return func(o *Options) {
|
||||
o.Name = n
|
||||
}
|
||||
}
|
||||
|
@@ -15,10 +15,14 @@ var (
|
||||
|
||||
// Tracer is an interface for distributed tracing
|
||||
type Tracer interface {
|
||||
Name() string
|
||||
Init(...Option) error
|
||||
// Start a trace
|
||||
Start(ctx context.Context, name string) (context.Context, *Span)
|
||||
// Finish the trace
|
||||
Finish(*Span) error
|
||||
// Lookup get span from context
|
||||
Lookup(ctx context.Context) (*Span, error)
|
||||
// Read the traces
|
||||
Read(...ReadOption) ([]*Span, error)
|
||||
}
|
@@ -10,10 +10,9 @@ import (
|
||||
|
||||
func FromRequest(r *http.Request) context.Context {
|
||||
ctx := r.Context()
|
||||
md, ok := metadata.FromContext(ctx)
|
||||
md, ok := metadata.FromIncomingContext(ctx)
|
||||
if !ok {
|
||||
// create needed map with specific len
|
||||
md = make(metadata.Metadata, len(r.Header)+2)
|
||||
md = metadata.New(len(r.Header) + 2)
|
||||
}
|
||||
for key, val := range r.Header {
|
||||
md.Set(key, strings.Join(val, ","))
|
||||
@@ -22,5 +21,5 @@ func FromRequest(r *http.Request) context.Context {
|
||||
md["Host"] = r.Host
|
||||
// pass http method
|
||||
md["Method"] = r.Method
|
||||
return metadata.NewContext(ctx, md)
|
||||
return metadata.NewIncomingContext(ctx, md)
|
||||
}
|
||||
|
@@ -28,7 +28,7 @@ func TestRequestToContext(t *testing.T) {
|
||||
|
||||
for _, d := range testData {
|
||||
ctx := FromRequest(d.request)
|
||||
md, ok := metadata.FromContext(ctx)
|
||||
md, ok := metadata.FromIncomingContext(ctx)
|
||||
if !ok {
|
||||
t.Fatalf("Expected metadata for request %+v", d.request)
|
||||
}
|
||||
|
@@ -8,15 +8,15 @@ import (
|
||||
"net/http"
|
||||
"testing"
|
||||
|
||||
"github.com/unistack-org/micro/v3/registry"
|
||||
"github.com/unistack-org/micro/v3/registry/memory"
|
||||
"github.com/unistack-org/micro/v3/register"
|
||||
"github.com/unistack-org/micro/v3/register/memory"
|
||||
"github.com/unistack-org/micro/v3/router"
|
||||
regRouter "github.com/unistack-org/micro/v3/router/registry"
|
||||
regRouter "github.com/unistack-org/micro/v3/router/register"
|
||||
)
|
||||
|
||||
func TestRoundTripper(t *testing.T) {
|
||||
m := memory.NewRegistry()
|
||||
r := regRouter.NewRouter(router.Registry(m))
|
||||
m := memory.NewRegister()
|
||||
r := regRouter.NewRouter(router.Register(m))
|
||||
|
||||
rt := NewRoundTripper(WithRouter(r))
|
||||
|
||||
@@ -32,9 +32,9 @@ func TestRoundTripper(t *testing.T) {
|
||||
|
||||
go http.Serve(l, nil)
|
||||
|
||||
m.Register(®istry.Service{
|
||||
m.Register(®ister.Service{
|
||||
Name: "example.com",
|
||||
Nodes: []*registry.Node{
|
||||
Nodes: []*register.Node{
|
||||
{
|
||||
Id: "1",
|
||||
Address: l.Addr().String(),
|
||||
|
@@ -2,24 +2,53 @@ package reflect
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/url"
|
||||
"reflect"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"strings"
|
||||
"unicode"
|
||||
)
|
||||
|
||||
var (
|
||||
bracketSplitter = regexp.MustCompile(`\[|\]`)
|
||||
ErrInvalidStruct = errors.New("invalid struct specified")
|
||||
ErrInvalidParam = errors.New("invalid url query param provided")
|
||||
)
|
||||
|
||||
func fieldName(name string) string {
|
||||
newstr := make([]rune, 0)
|
||||
upper := false
|
||||
for idx, chr := range name {
|
||||
if idx == 0 {
|
||||
upper = true
|
||||
} else if chr == '_' {
|
||||
upper = true
|
||||
continue
|
||||
}
|
||||
if upper {
|
||||
newstr = append(newstr, unicode.ToUpper(chr))
|
||||
} else {
|
||||
newstr = append(newstr, chr)
|
||||
}
|
||||
upper = false
|
||||
}
|
||||
|
||||
return string(newstr)
|
||||
}
|
||||
|
||||
func IsEmpty(v reflect.Value) bool {
|
||||
switch v.Kind() {
|
||||
switch getKind(v) {
|
||||
case reflect.Array, reflect.Map, reflect.Slice, reflect.String:
|
||||
return v.Len() == 0
|
||||
case reflect.Bool:
|
||||
return !v.Bool()
|
||||
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
|
||||
case reflect.Int:
|
||||
return v.Int() == 0
|
||||
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
|
||||
case reflect.Uint:
|
||||
return v.Uint() == 0
|
||||
case reflect.Float32, reflect.Float64:
|
||||
case reflect.Float32:
|
||||
return v.Float() == 0
|
||||
case reflect.Interface, reflect.Ptr:
|
||||
if v.IsNil() {
|
||||
@@ -119,3 +148,450 @@ func CopyFrom(a, b interface{}) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func URLMap(query string) (map[string]interface{}, error) {
|
||||
var (
|
||||
mp interface{} = make(map[string]interface{})
|
||||
)
|
||||
|
||||
params := strings.Split(query, "&")
|
||||
|
||||
for _, part := range params {
|
||||
tm, err := queryToMap(part)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
mp = merge(mp, tm)
|
||||
}
|
||||
|
||||
return mp.(map[string]interface{}), nil
|
||||
}
|
||||
|
||||
func FlattenMap(a map[string]interface{}) map[string]interface{} {
|
||||
// preprocess map
|
||||
nb := make(map[string]interface{}, len(a))
|
||||
for k, v := range a {
|
||||
ps := strings.Split(k, ".")
|
||||
if len(ps) == 1 {
|
||||
nb[k] = v
|
||||
continue
|
||||
}
|
||||
em := make(map[string]interface{})
|
||||
em[ps[len(ps)-1]] = v
|
||||
for i := len(ps) - 2; i > 0; i-- {
|
||||
nm := make(map[string]interface{})
|
||||
nm[ps[i]] = em
|
||||
em = nm
|
||||
}
|
||||
if vm, ok := nb[ps[0]]; ok {
|
||||
// nested map
|
||||
nm := vm.(map[string]interface{})
|
||||
for vk, vv := range em {
|
||||
nm[vk] = vv
|
||||
}
|
||||
nb[ps[0]] = nm
|
||||
} else {
|
||||
nb[ps[0]] = em
|
||||
}
|
||||
}
|
||||
return nb
|
||||
}
|
||||
|
||||
func MergeMap(a interface{}, b map[string]interface{}) error {
|
||||
var err error
|
||||
|
||||
ta := reflect.TypeOf(a)
|
||||
if ta.Kind() == reflect.Ptr {
|
||||
ta = ta.Elem()
|
||||
}
|
||||
va := reflect.ValueOf(a)
|
||||
if va.Kind() == reflect.Ptr {
|
||||
va = va.Elem()
|
||||
}
|
||||
|
||||
for mk, mv := range b {
|
||||
vmv := reflect.ValueOf(mv)
|
||||
name := fieldName(mk)
|
||||
fva := va.FieldByName(name)
|
||||
fta, found := ta.FieldByName(name)
|
||||
if !found || !fva.IsValid() || !fva.CanSet() || fta.PkgPath != "" {
|
||||
continue
|
||||
}
|
||||
// fast path via direct assign
|
||||
if vmv.Type().AssignableTo(fta.Type) && !IsEmpty(vmv) {
|
||||
fva.Set(vmv)
|
||||
continue
|
||||
}
|
||||
switch getKind(fva) {
|
||||
case reflect.Bool:
|
||||
err = mergeBool(fva, vmv)
|
||||
case reflect.String:
|
||||
err = mergeString(fva, vmv)
|
||||
case reflect.Int:
|
||||
err = mergeInt(fva, vmv)
|
||||
case reflect.Uint:
|
||||
err = mergeUint(fva, vmv)
|
||||
case reflect.Float32:
|
||||
err = mergeFloat(fva, vmv)
|
||||
case reflect.Array:
|
||||
//fmt.Printf("Array %#+v %#+v\n", fva, vmv)
|
||||
case reflect.Slice:
|
||||
err = mergeSlice(fva, vmv)
|
||||
case reflect.Ptr:
|
||||
if fva.IsNil() {
|
||||
fva.Set(reflect.New(fva.Type().Elem()))
|
||||
if fva.Elem().Type().Kind() == reflect.Struct {
|
||||
for i := 0; i < fva.Elem().NumField(); i++ {
|
||||
field := fva.Elem().Field(i)
|
||||
if field.Type().Kind() == reflect.Ptr && field.IsNil() && fva.Elem().Type().Field(i).Anonymous == true {
|
||||
field.Set(reflect.New(field.Type().Elem()))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if nmp, ok := vmv.Interface().(map[string]interface{}); ok {
|
||||
err = MergeMap(fva.Interface(), nmp)
|
||||
} else {
|
||||
err = fmt.Errorf("cant fill")
|
||||
}
|
||||
case reflect.Struct:
|
||||
if nmp, ok := vmv.Interface().(map[string]interface{}); ok {
|
||||
err = MergeMap(fva.Interface(), nmp)
|
||||
} else {
|
||||
err = fmt.Errorf("cant fill")
|
||||
}
|
||||
case reflect.Map:
|
||||
//fmt.Printf("Map %#+v %#+v\n", fva, vmv)
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func mergeSlice(va, vb reflect.Value) error {
|
||||
switch getKind(vb) {
|
||||
/*
|
||||
case reflect.Int:
|
||||
if vb.Int() == 1 {
|
||||
va.SetBool(true)
|
||||
}
|
||||
case reflect.Uint:
|
||||
if vb.Uint() == 1 {
|
||||
va.SetBool(true)
|
||||
}
|
||||
case reflect.Float64:
|
||||
if vb.Float() == 1 {
|
||||
va.SetBool(true)
|
||||
}
|
||||
*/
|
||||
case reflect.String:
|
||||
var err error
|
||||
fn := func(c rune) bool { return c == ',' || c == ';' || c == ' ' }
|
||||
slice := strings.FieldsFunc(vb.String(), fn)
|
||||
va.Set(reflect.MakeSlice(va.Type(), len(slice), len(slice)))
|
||||
for idx, sl := range slice {
|
||||
vl := reflect.ValueOf(sl)
|
||||
switch va.Type().Elem().Kind() {
|
||||
case reflect.Bool:
|
||||
err = mergeBool(va.Index(idx), vl)
|
||||
case reflect.String:
|
||||
err = mergeString(va.Index(idx), vl)
|
||||
case reflect.Ptr:
|
||||
if va.Index(idx).IsNil() {
|
||||
va.Index(idx).Set(reflect.New(va.Index(idx).Type().Elem()))
|
||||
}
|
||||
switch va.Type().Elem().String() {
|
||||
case "*wrapperspb.BoolValue":
|
||||
if eva := reflect.Indirect(va.Index(idx)).FieldByName("Value"); eva.IsValid() {
|
||||
err = mergeBool(eva, vl)
|
||||
}
|
||||
case "*wrapperspb.BytesValue":
|
||||
if eva := va.Index(idx).FieldByName("Value"); eva.IsValid() {
|
||||
err = mergeUint(eva, vl)
|
||||
}
|
||||
case "*wrapperspb.DoubleValue", "*wrapperspb.FloatValue":
|
||||
if eva := reflect.Indirect(va.Index(idx)).FieldByName("Value"); eva.IsValid() {
|
||||
err = mergeFloat(eva, vl)
|
||||
}
|
||||
case "*wrapperspb.Int32Value", "*wrapperspb.Int64Value":
|
||||
if eva := reflect.Indirect(va.Index(idx)).FieldByName("Value"); eva.IsValid() {
|
||||
err = mergeInt(eva, vl)
|
||||
}
|
||||
case "*wrapperspb.StringValue":
|
||||
if eva := reflect.Indirect(va.Index(idx)).FieldByName("Value"); eva.IsValid() {
|
||||
err = mergeString(eva, vl)
|
||||
}
|
||||
case "*wrapperspb.UInt32Value", "*wrapperspb.UInt64Value":
|
||||
if eva := reflect.Indirect(va.Index(idx)).FieldByName("Value"); eva.IsValid() {
|
||||
err = mergeUint(eva, vl)
|
||||
}
|
||||
}
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
default:
|
||||
return fmt.Errorf("cant merge %v %s with %v %s", va, va.Kind(), vb, vb.Kind())
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func mergeBool(va, vb reflect.Value) error {
|
||||
switch getKind(vb) {
|
||||
case reflect.Int:
|
||||
if vb.Int() == 1 {
|
||||
va.SetBool(true)
|
||||
}
|
||||
case reflect.Uint:
|
||||
if vb.Uint() == 1 {
|
||||
va.SetBool(true)
|
||||
}
|
||||
case reflect.Float32:
|
||||
if vb.Float() == 1 {
|
||||
va.SetBool(true)
|
||||
}
|
||||
case reflect.String:
|
||||
if b, err := strconv.ParseBool(vb.String()); err != nil {
|
||||
return err
|
||||
} else {
|
||||
va.SetBool(b)
|
||||
}
|
||||
default:
|
||||
return fmt.Errorf("cant merge %v %s with %v %s", va, va.Kind(), vb, vb.Kind())
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func mergeString(va, vb reflect.Value) error {
|
||||
switch getKind(vb) {
|
||||
case reflect.Int:
|
||||
va.SetString(fmt.Sprintf("%d", vb.Int()))
|
||||
case reflect.Uint:
|
||||
va.SetString(fmt.Sprintf("%d", vb.Uint()))
|
||||
case reflect.Float32:
|
||||
va.SetString(fmt.Sprintf("%f", vb.Float()))
|
||||
case reflect.String:
|
||||
va.Set(vb)
|
||||
default:
|
||||
return fmt.Errorf("cant merge %v %s with %v %s", va, va.Kind(), vb, vb.Kind())
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func mergeInt(va, vb reflect.Value) error {
|
||||
switch getKind(vb) {
|
||||
case reflect.Int:
|
||||
va.Set(vb)
|
||||
case reflect.Uint:
|
||||
va.SetInt(int64(vb.Uint()))
|
||||
case reflect.Float32:
|
||||
va.SetInt(int64(vb.Float()))
|
||||
case reflect.String:
|
||||
if f, err := strconv.ParseInt(vb.String(), 10, va.Type().Bits()); err != nil {
|
||||
return err
|
||||
} else {
|
||||
va.SetInt(f)
|
||||
}
|
||||
default:
|
||||
return fmt.Errorf("cant merge %v %s with %v %s", va, va.Kind(), vb, vb.Kind())
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func mergeUint(va, vb reflect.Value) error {
|
||||
switch getKind(vb) {
|
||||
case reflect.Int:
|
||||
va.SetUint(uint64(vb.Int()))
|
||||
case reflect.Uint:
|
||||
va.Set(vb)
|
||||
case reflect.Float32:
|
||||
va.SetUint(uint64(vb.Float()))
|
||||
case reflect.String:
|
||||
if f, err := strconv.ParseUint(vb.String(), 10, va.Type().Bits()); err != nil {
|
||||
return err
|
||||
} else {
|
||||
va.SetUint(f)
|
||||
}
|
||||
default:
|
||||
return fmt.Errorf("cant merge %v %s with %v %s", va, va.Kind(), vb, vb.Kind())
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func mergeFloat(va, vb reflect.Value) error {
|
||||
switch getKind(vb) {
|
||||
case reflect.Int:
|
||||
va.SetFloat(float64(vb.Int()))
|
||||
case reflect.Uint:
|
||||
va.SetFloat(float64(vb.Uint()))
|
||||
case reflect.Float32:
|
||||
va.Set(vb)
|
||||
case reflect.String:
|
||||
if f, err := strconv.ParseFloat(vb.String(), va.Type().Bits()); err != nil {
|
||||
return err
|
||||
} else {
|
||||
va.SetFloat(f)
|
||||
}
|
||||
default:
|
||||
return fmt.Errorf("cant merge %v %s with %v %s", va, va.Kind(), vb, vb.Kind())
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func getKind(val reflect.Value) reflect.Kind {
|
||||
kind := val.Kind()
|
||||
switch {
|
||||
case kind >= reflect.Int && kind <= reflect.Int64:
|
||||
return reflect.Int
|
||||
case kind >= reflect.Uint && kind <= reflect.Uint64:
|
||||
return reflect.Uint
|
||||
case kind >= reflect.Float32 && kind <= reflect.Float64:
|
||||
return reflect.Float32
|
||||
}
|
||||
return kind
|
||||
}
|
||||
|
||||
func btSplitter(str string) []string {
|
||||
r := bracketSplitter.Split(str, -1)
|
||||
for idx, s := range r {
|
||||
if len(s) == 0 {
|
||||
if len(r) > idx+1 {
|
||||
copy(r[idx:], r[idx+1:])
|
||||
r = r[:len(r)-1]
|
||||
}
|
||||
}
|
||||
}
|
||||
return r
|
||||
}
|
||||
|
||||
// queryToMap turns something like a[b][c]=4 into
|
||||
// map[string]interface{}{
|
||||
// "a": map[string]interface{}{
|
||||
// "b": map[string]interface{}{
|
||||
// "c": 4,
|
||||
// },
|
||||
// },
|
||||
// }
|
||||
func queryToMap(param string) (map[string]interface{}, error) {
|
||||
rawKey, rawValue, err := splitKeyAndValue(param)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
rawValue, err = url.QueryUnescape(rawValue)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
rawKey, err = url.QueryUnescape(rawKey)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
pieces := btSplitter(rawKey)
|
||||
key := pieces[0]
|
||||
|
||||
// If len==1 then rawKey has no [] chars and we can just
|
||||
// decode this as key=value into {key: value}
|
||||
if len(pieces) == 1 {
|
||||
return map[string]interface{}{
|
||||
key: rawValue,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// If len > 1 then we have something like a[b][c]=2
|
||||
// so we need to turn this into {"a": {"b": {"c": 2}}}
|
||||
// To do this we break our key into two pieces:
|
||||
// a and b[c]
|
||||
// and then we set {"a": queryToMap("b[c]", value)}
|
||||
ret := make(map[string]interface{})
|
||||
ret[key], err = queryToMap(buildNewKey(rawKey) + "=" + rawValue)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// When URL params have a set of empty brackets (eg a[]=1)
|
||||
// it is assumed to be an array. This will get us the
|
||||
// correct value for the array item and return it as an
|
||||
// []interface{} so that it can be merged properly.
|
||||
if pieces[1] == "" {
|
||||
temp := ret[key].(map[string]interface{})
|
||||
ret[key] = []interface{}{temp[""]}
|
||||
}
|
||||
return ret, nil
|
||||
}
|
||||
|
||||
// buildNewKey will take something like:
|
||||
// origKey = "bar[one][two]"
|
||||
// pieces = [bar one two ]
|
||||
// and return "one[two]"
|
||||
func buildNewKey(origKey string) string {
|
||||
pieces := btSplitter(origKey)
|
||||
|
||||
ret := origKey[len(pieces[0])+1:]
|
||||
ret = ret[:len(pieces[1])] + ret[len(pieces[1])+1:]
|
||||
return ret
|
||||
}
|
||||
|
||||
// splitKeyAndValue splits a URL param at the last equal
|
||||
// sign and returns the two strings. If no equal sign is
|
||||
// found, the ErrInvalidParam error is returned.
|
||||
func splitKeyAndValue(param string) (string, string, error) {
|
||||
li := strings.LastIndex(param, "=")
|
||||
if li == -1 {
|
||||
return "", "", ErrInvalidParam
|
||||
}
|
||||
return param[:li], param[li+1:], nil
|
||||
}
|
||||
|
||||
// merge merges a with b if they are either both slices
|
||||
// or map[string]interface{} types. Otherwise it returns b.
|
||||
func merge(a interface{}, b interface{}) interface{} {
|
||||
if av, aok := a.(map[string]interface{}); aok {
|
||||
if bv, bok := b.(map[string]interface{}); bok {
|
||||
return mergeMapIface(av, bv)
|
||||
}
|
||||
}
|
||||
if av, aok := a.([]interface{}); aok {
|
||||
if bv, bok := b.([]interface{}); bok {
|
||||
return mergeSliceIface(av, bv)
|
||||
}
|
||||
}
|
||||
|
||||
va := reflect.ValueOf(a)
|
||||
vb := reflect.ValueOf(b)
|
||||
if (va.Type().Kind() == reflect.Slice) && (va.Type().Elem().Kind() == vb.Type().Kind() || vb.Type().ConvertibleTo(va.Type().Elem())) {
|
||||
va = reflect.Append(va, vb.Convert(va.Type().Elem()))
|
||||
return va.Interface()
|
||||
}
|
||||
|
||||
return b
|
||||
}
|
||||
|
||||
// mergeMap merges a with b, attempting to merge any nested
|
||||
// values in nested maps but eventually overwriting anything
|
||||
// in a that can't be merged with whatever is in b.
|
||||
func mergeMapIface(a map[string]interface{}, b map[string]interface{}) map[string]interface{} {
|
||||
for bK, bV := range b {
|
||||
if aV, ok := a[bK]; ok {
|
||||
if (reflect.ValueOf(aV).Type().Kind() == reflect.ValueOf(bV).Type().Kind()) ||
|
||||
((reflect.ValueOf(aV).Type().Kind() == reflect.Slice) && reflect.ValueOf(aV).Type().Elem().Kind() == reflect.ValueOf(bV).Type().Kind()) {
|
||||
nV := []interface{}{aV, bV}
|
||||
a[bK] = nV
|
||||
} else {
|
||||
a[bK] = merge(a[bK], bV)
|
||||
}
|
||||
} else {
|
||||
a[bK] = bV
|
||||
}
|
||||
}
|
||||
return a
|
||||
}
|
||||
|
||||
// mergeSlice merges a with b and returns the result.
|
||||
func mergeSliceIface(a []interface{}, b []interface{}) []interface{} {
|
||||
a = append(a, b...)
|
||||
return a
|
||||
}
|
||||
|
45
util/reflect/reflect_test.go
Normal file
45
util/reflect/reflect_test.go
Normal file
@@ -0,0 +1,45 @@
|
||||
package reflect
|
||||
|
||||
import (
|
||||
"net/url"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestURLSliceVars(t *testing.T) {
|
||||
u, err := url.Parse("http://localhost/v1/test/call/my_name?key=arg1&key=arg2&key=arg3")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
mp, err := URLMap(u.RawQuery)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
v, ok := mp["key"]
|
||||
if !ok {
|
||||
t.Fatalf("key not exists: %#+v", mp)
|
||||
}
|
||||
|
||||
vm, ok := v.([]interface{})
|
||||
if !ok {
|
||||
t.Fatalf("invalid key value")
|
||||
}
|
||||
|
||||
if len(vm) != 3 {
|
||||
t.Fatalf("missing key value: %#+v", mp)
|
||||
}
|
||||
}
|
||||
|
||||
func TestURLVars(t *testing.T) {
|
||||
u, err := url.Parse("http://localhost/v1/test/call/my_name?req=key&arg1=arg1&arg2=12345&nested.string_args=str1&nested.string_args=str2&arg2=54321")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
mp, err := URLMap(u.RawQuery)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
_ = mp
|
||||
}
|
@@ -1,11 +1,11 @@
|
||||
package registry
|
||||
package register
|
||||
|
||||
import (
|
||||
"github.com/unistack-org/micro/v3/registry"
|
||||
"github.com/unistack-org/micro/v3/register"
|
||||
)
|
||||
|
||||
func addNodes(old, neu []*registry.Node) []*registry.Node {
|
||||
nodes := make([]*registry.Node, len(neu))
|
||||
func addNodes(old, neu []*register.Node) []*register.Node {
|
||||
nodes := make([]*register.Node, len(neu))
|
||||
// add all new nodes
|
||||
for i, n := range neu {
|
||||
node := *n
|
||||
@@ -35,8 +35,8 @@ func addNodes(old, neu []*registry.Node) []*registry.Node {
|
||||
return nodes
|
||||
}
|
||||
|
||||
func delNodes(old, del []*registry.Node) []*registry.Node {
|
||||
var nodes []*registry.Node
|
||||
func delNodes(old, del []*register.Node) []*register.Node {
|
||||
var nodes []*register.Node
|
||||
for _, o := range old {
|
||||
var rem bool
|
||||
for _, n := range del {
|
||||
@@ -53,24 +53,24 @@ func delNodes(old, del []*registry.Node) []*registry.Node {
|
||||
}
|
||||
|
||||
// CopyService make a copy of service
|
||||
func CopyService(service *registry.Service) *registry.Service {
|
||||
func CopyService(service *register.Service) *register.Service {
|
||||
// copy service
|
||||
s := ®istry.Service{}
|
||||
s := ®ister.Service{}
|
||||
*s = *service
|
||||
|
||||
// copy nodes
|
||||
nodes := make([]*registry.Node, len(service.Nodes))
|
||||
nodes := make([]*register.Node, len(service.Nodes))
|
||||
for j, node := range service.Nodes {
|
||||
n := ®istry.Node{}
|
||||
n := ®ister.Node{}
|
||||
*n = *node
|
||||
nodes[j] = n
|
||||
}
|
||||
s.Nodes = nodes
|
||||
|
||||
// copy endpoints
|
||||
eps := make([]*registry.Endpoint, len(service.Endpoints))
|
||||
eps := make([]*register.Endpoint, len(service.Endpoints))
|
||||
for j, ep := range service.Endpoints {
|
||||
e := ®istry.Endpoint{}
|
||||
e := ®ister.Endpoint{}
|
||||
*e = *ep
|
||||
eps[j] = e
|
||||
}
|
||||
@@ -79,8 +79,8 @@ func CopyService(service *registry.Service) *registry.Service {
|
||||
}
|
||||
|
||||
// Copy makes a copy of services
|
||||
func Copy(current []*registry.Service) []*registry.Service {
|
||||
services := make([]*registry.Service, len(current))
|
||||
func Copy(current []*register.Service) []*register.Service {
|
||||
services := make([]*register.Service, len(current))
|
||||
for i, service := range current {
|
||||
services[i] = CopyService(service)
|
||||
}
|
||||
@@ -88,14 +88,14 @@ func Copy(current []*registry.Service) []*registry.Service {
|
||||
}
|
||||
|
||||
// Merge merges two lists of services and returns a new copy
|
||||
func Merge(olist []*registry.Service, nlist []*registry.Service) []*registry.Service {
|
||||
var srv []*registry.Service
|
||||
func Merge(olist []*register.Service, nlist []*register.Service) []*register.Service {
|
||||
var srv []*register.Service
|
||||
|
||||
for _, n := range nlist {
|
||||
var seen bool
|
||||
for _, o := range olist {
|
||||
if o.Version == n.Version {
|
||||
sp := ®istry.Service{}
|
||||
sp := ®ister.Service{}
|
||||
// make copy
|
||||
*sp = *o
|
||||
// set nodes
|
||||
@@ -106,25 +106,25 @@ func Merge(olist []*registry.Service, nlist []*registry.Service) []*registry.Ser
|
||||
srv = append(srv, sp)
|
||||
break
|
||||
} else {
|
||||
sp := ®istry.Service{}
|
||||
sp := ®ister.Service{}
|
||||
// make copy
|
||||
*sp = *o
|
||||
srv = append(srv, sp)
|
||||
}
|
||||
}
|
||||
if !seen {
|
||||
srv = append(srv, Copy([]*registry.Service{n})...)
|
||||
srv = append(srv, Copy([]*register.Service{n})...)
|
||||
}
|
||||
}
|
||||
return srv
|
||||
}
|
||||
|
||||
// Remove removes services and returns a new copy
|
||||
func Remove(old, del []*registry.Service) []*registry.Service {
|
||||
var services []*registry.Service
|
||||
func Remove(old, del []*register.Service) []*register.Service {
|
||||
var services []*register.Service
|
||||
|
||||
for _, o := range old {
|
||||
srv := ®istry.Service{}
|
||||
srv := ®ister.Service{}
|
||||
*srv = *o
|
||||
|
||||
var rem bool
|
@@ -1,18 +1,18 @@
|
||||
package registry
|
||||
package register
|
||||
|
||||
import (
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
"github.com/unistack-org/micro/v3/registry"
|
||||
"github.com/unistack-org/micro/v3/register"
|
||||
)
|
||||
|
||||
func TestRemove(t *testing.T) {
|
||||
services := []*registry.Service{
|
||||
services := []*register.Service{
|
||||
{
|
||||
Name: "foo",
|
||||
Version: "1.0.0",
|
||||
Nodes: []*registry.Node{
|
||||
Nodes: []*register.Node{
|
||||
{
|
||||
Id: "foo-123",
|
||||
Address: "localhost:9999",
|
||||
@@ -22,7 +22,7 @@ func TestRemove(t *testing.T) {
|
||||
{
|
||||
Name: "foo",
|
||||
Version: "1.0.0",
|
||||
Nodes: []*registry.Node{
|
||||
Nodes: []*register.Node{
|
||||
{
|
||||
Id: "foo-123",
|
||||
Address: "localhost:6666",
|
||||
@@ -31,7 +31,7 @@ func TestRemove(t *testing.T) {
|
||||
},
|
||||
}
|
||||
|
||||
servs := Remove([]*registry.Service{services[0]}, []*registry.Service{services[1]})
|
||||
servs := Remove([]*register.Service{services[0]}, []*register.Service{services[1]})
|
||||
if i := len(servs); i > 0 {
|
||||
t.Errorf("Expected 0 nodes, got %d: %+v", i, servs)
|
||||
}
|
||||
@@ -41,11 +41,11 @@ func TestRemove(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestRemoveNodes(t *testing.T) {
|
||||
services := []*registry.Service{
|
||||
services := []*register.Service{
|
||||
{
|
||||
Name: "foo",
|
||||
Version: "1.0.0",
|
||||
Nodes: []*registry.Node{
|
||||
Nodes: []*register.Node{
|
||||
{
|
||||
Id: "foo-123",
|
||||
Address: "localhost:9999",
|
||||
@@ -59,7 +59,7 @@ func TestRemoveNodes(t *testing.T) {
|
||||
{
|
||||
Name: "foo",
|
||||
Version: "1.0.0",
|
||||
Nodes: []*registry.Node{
|
||||
Nodes: []*register.Node{
|
||||
{
|
||||
Id: "foo-123",
|
||||
Address: "localhost:6666",
|
@@ -1,7 +1,7 @@
|
||||
package router
|
||||
|
||||
import (
|
||||
"github.com/unistack-org/micro/v3/registry"
|
||||
"github.com/unistack-org/micro/v3/register"
|
||||
"github.com/unistack-org/micro/v3/router"
|
||||
)
|
||||
|
||||
@@ -19,7 +19,7 @@ func (r *apiRouter) String() string {
|
||||
}
|
||||
|
||||
// New router is a hack for API routing
|
||||
func New(srvs []*registry.Service) router.Router {
|
||||
func New(srvs []*register.Service) router.Router {
|
||||
var routes []router.Route
|
||||
|
||||
for _, srv := range srvs {
|
||||
|
@@ -78,6 +78,11 @@ func (c *syncStore) Init(opts ...store.Option) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Name returns the store name
|
||||
func (c *syncStore) Name() string {
|
||||
return c.storeOpts.Name
|
||||
}
|
||||
|
||||
// Options returns the sync's store options
|
||||
func (c *syncStore) Options() store.Options {
|
||||
return c.storeOpts
|
||||
@@ -96,7 +101,7 @@ func (c *syncStore) List(ctx context.Context, opts ...store.ListOption) ([]strin
|
||||
return c.syncOpts.Stores[0].List(ctx, opts...)
|
||||
}
|
||||
|
||||
func (c *syncStore) Exists(ctx context.Context, key string) error {
|
||||
func (c *syncStore) Exists(ctx context.Context, key string, opts ...store.ExistsOption) error {
|
||||
return c.syncOpts.Stores[0].Exists(ctx, key)
|
||||
}
|
||||
|
||||
|
Reference in New Issue
Block a user