broker refactor
Some checks failed
coverage / build (pull_request) Failing after 36s
lint / lint (pull_request) Failing after 2m55s
test / test (pull_request) Successful in 4m28s

Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
This commit is contained in:
Василий Толстов 2025-01-30 01:44:11 +03:00
parent c9066e0455
commit c5e6d4cddc
16 changed files with 785 additions and 688 deletions

View File

@ -20,6 +20,8 @@ var (
ErrDisconnected = errors.New("broker disconnected") ErrDisconnected = errors.New("broker disconnected")
// ErrInvalidMessage returns when invalid Message passed // ErrInvalidMessage returns when invalid Message passed
ErrInvalidMessage = errors.New("invalid message") ErrInvalidMessage = errors.New("invalid message")
// ErrInvalidHandler returns when subscriber passed to Subscribe
ErrInvalidHandler = errors.New("invalid handler")
// DefaultGracefulTimeout // DefaultGracefulTimeout
DefaultGracefulTimeout = 5 * time.Second DefaultGracefulTimeout = 5 * time.Second
) )
@ -87,9 +89,3 @@ type Subscriber interface {
// Unsubscribe from topic // Unsubscribe from topic
Unsubscribe(ctx context.Context) error Unsubscribe(ctx context.Context) error
} }
// MessageHandler func signature for single message processing
type MessageHandler func(Message) error
// MessagesHandler func signature for batch message processing
type MessagesHandler func([]Message) error

View File

@ -2,6 +2,7 @@ package broker
import ( import (
"context" "context"
"strings"
"sync" "sync"
"go.unistack.org/micro/v4/broker" "go.unistack.org/micro/v4/broker"
@ -34,6 +35,30 @@ type memoryMessage struct {
opts broker.PublishOptions opts broker.PublishOptions
} }
func (m *memoryMessage) Ack() error {
return nil
}
func (m *memoryMessage) Body() []byte {
return m.body
}
func (m *memoryMessage) Header() metadata.Metadata {
return m.hdr
}
func (m *memoryMessage) Context() context.Context {
return m.ctx
}
func (m *memoryMessage) Topic() string {
return ""
}
func (m *memoryMessage) Unmarshal(dst interface{}, opts ...codec.Option) error {
return m.c.Unmarshal(m.body, dst)
}
type Subscriber struct { type Subscriber struct {
ctx context.Context ctx context.Context
exit chan bool exit chan bool
@ -43,25 +68,38 @@ type Subscriber struct {
opts broker.SubscribeOptions opts broker.SubscribeOptions
} }
func (m *Broker) Options() broker.Options { func (b *Broker) newCodec(ct string) (codec.Codec, error) {
return m.opts if idx := strings.IndexRune(ct, ';'); idx >= 0 {
ct = ct[:idx]
}
b.RLock()
c, ok := b.opts.Codecs[ct]
b.RUnlock()
if ok {
return c, nil
}
return nil, codec.ErrUnknownContentType
} }
func (m *Broker) Address() string { func (b *Broker) Options() broker.Options {
return m.addr return b.opts
} }
func (m *Broker) Connect(ctx context.Context) error { func (b *Broker) Address() string {
return b.addr
}
func (b *Broker) Connect(ctx context.Context) error {
select { select {
case <-ctx.Done(): case <-ctx.Done():
return ctx.Err() return ctx.Err()
default: default:
} }
m.Lock() b.Lock()
defer m.Unlock() defer b.Unlock()
if m.connected { if b.connected {
return nil return nil
} }
@ -75,65 +113,79 @@ func (m *Broker) Connect(ctx context.Context) error {
// set addr with port // set addr with port
addr = mnet.HostPort(addr, 10000+i) addr = mnet.HostPort(addr, 10000+i)
m.addr = addr b.addr = addr
m.connected = true b.connected = true
return nil return nil
} }
func (m *Broker) Disconnect(ctx context.Context) error { func (b *Broker) Disconnect(ctx context.Context) error {
select { select {
case <-ctx.Done(): case <-ctx.Done():
return ctx.Err() return ctx.Err()
default: default:
} }
m.Lock() b.Lock()
defer m.Unlock() defer b.Unlock()
if !m.connected { if !b.connected {
return nil return nil
} }
m.connected = false b.connected = false
return nil return nil
} }
func (m *Broker) Init(opts ...broker.Option) error { func (b *Broker) Init(opts ...broker.Option) error {
for _, o := range opts { for _, o := range opts {
o(&m.opts) o(&b.opts)
} }
m.funcPublish = m.fnPublish b.funcPublish = b.fnPublish
m.funcSubscribe = m.fnSubscribe b.funcSubscribe = b.fnSubscribe
m.opts.Hooks.EachPrev(func(hook options.Hook) { b.opts.Hooks.EachPrev(func(hook options.Hook) {
switch h := hook.(type) { switch h := hook.(type) {
case broker.HookPublish: case broker.HookPublish:
m.funcPublish = h(m.funcPublish) b.funcPublish = h(b.funcPublish)
case broker.HookSubscribe: case broker.HookSubscribe:
m.funcSubscribe = h(m.funcSubscribe) b.funcSubscribe = h(b.funcSubscribe)
} }
}) })
return nil return nil
} }
func (m *Broker) Publish(ctx context.Context, topic string, messages ...broker.Message) error { func (b *Broker) NewMessage(ctx context.Context, hdr metadata.Metadata, body interface{}, opts ...broker.PublishOption) (broker.Message, error) {
return m.funcPublish(ctx, topic, messages...) options := broker.NewPublishOptions(opts...)
m := &memoryMessage{ctx: ctx, hdr: hdr, opts: options}
c, err := b.newCodec(m.opts.ContentType)
if err == nil {
m.body, err = c.Marshal(body)
}
if err != nil {
return nil, err
}
return m, nil
} }
func (m *Broker) fnPublish(ctx context.Context, topic string, messages ...broker.Message) error { func (b *Broker) Publish(ctx context.Context, topic string, messages ...broker.Message) error {
return m.publish(ctx, topic, messages...) return b.funcPublish(ctx, topic, messages...)
} }
func (m *Broker) publish(ctx context.Context, topic string, messages ...broker.Message) error { func (b *Broker) fnPublish(ctx context.Context, topic string, messages ...broker.Message) error {
m.RLock() return b.publish(ctx, topic, messages...)
if !m.connected { }
m.RUnlock()
func (b *Broker) publish(ctx context.Context, topic string, messages ...broker.Message) error {
b.RLock()
if !b.connected {
b.RUnlock()
return broker.ErrNotConnected return broker.ErrNotConnected
} }
m.RUnlock() b.RUnlock()
select { select {
case <-ctx.Done(): case <-ctx.Done():
@ -141,9 +193,9 @@ func (m *Broker) publish(ctx context.Context, topic string, messages ...broker.M
default: default:
} }
m.RLock() b.RLock()
subs, ok := m.subscribers[topic] subs, ok := b.subscribers[topic]
m.RUnlock() b.RUnlock()
if !ok { if !ok {
return nil return nil
} }
@ -152,24 +204,28 @@ func (m *Broker) publish(ctx context.Context, topic string, messages ...broker.M
for _, sub := range subs { for _, sub := range subs {
switch s := sub.handler.(type) { switch s := sub.handler.(type) {
case broker.MessageHandler: default:
if b.opts.Logger.V(logger.ErrorLevel) {
b.opts.Logger.Error(ctx, "broker handler error", broker.ErrInvalidHandler)
}
case func(broker.Message) error:
for _, message := range messages { for _, message := range messages {
if err = s(message); err == nil && sub.opts.AutoAck { if err = s(message); err == nil && sub.opts.AutoAck {
err = message.Ack() err = message.Ack()
} }
if err != nil { if err != nil {
if m.opts.Logger.V(logger.ErrorLevel) { if b.opts.Logger.V(logger.ErrorLevel) {
m.opts.Logger.Error(m.opts.Context, "broker handler error", err) b.opts.Logger.Error(ctx, "broker handler error", err)
} }
} }
} }
case broker.MessagesHandler: case func([]broker.Message) error:
if err = s(messages); err == nil && sub.opts.AutoAck { if err = s(messages); err == nil && sub.opts.AutoAck {
for _, message := range messages { for _, message := range messages {
err = message.Ack() err = message.Ack()
if err != nil { if err != nil {
if m.opts.Logger.V(logger.ErrorLevel) { if b.opts.Logger.V(logger.ErrorLevel) {
m.opts.Logger.Error(m.opts.Context, "broker handler error", err) b.opts.Logger.Error(ctx, "broker handler error", err)
} }
} }
} }
@ -180,17 +236,21 @@ func (m *Broker) publish(ctx context.Context, topic string, messages ...broker.M
return nil return nil
} }
func (m *Broker) Subscribe(ctx context.Context, topic string, handler interface{}, opts ...broker.SubscribeOption) (broker.Subscriber, error) { func (b *Broker) Subscribe(ctx context.Context, topic string, handler interface{}, opts ...broker.SubscribeOption) (broker.Subscriber, error) {
return m.funcSubscribe(ctx, topic, handler, opts...) return b.funcSubscribe(ctx, topic, handler, opts...)
} }
func (m *Broker) fnSubscribe(ctx context.Context, topic string, handler interface{}, opts ...broker.SubscribeOption) (broker.Subscriber, error) { func (b *Broker) fnSubscribe(ctx context.Context, topic string, handler interface{}, opts ...broker.SubscribeOption) (broker.Subscriber, error) {
m.RLock() if err := broker.IsValidHandler(handler); err != nil {
if !m.connected { return nil, err
m.RUnlock() }
b.RLock()
if !b.connected {
b.RUnlock()
return nil, broker.ErrNotConnected return nil, broker.ErrNotConnected
} }
m.RUnlock() b.RUnlock()
sid, err := id.New() sid, err := id.New()
if err != nil { if err != nil {
@ -208,63 +268,47 @@ func (m *Broker) fnSubscribe(ctx context.Context, topic string, handler interfac
ctx: ctx, ctx: ctx,
} }
m.Lock() b.Lock()
m.subscribers[topic] = append(m.subscribers[topic], sub) b.subscribers[topic] = append(b.subscribers[topic], sub)
m.Unlock() b.Unlock()
go func() { go func() {
<-sub.exit <-sub.exit
m.Lock() b.Lock()
newSubscribers := make([]*Subscriber, 0, len(m.subscribers)-1) newSubscribers := make([]*Subscriber, 0, len(b.subscribers)-1)
for _, sb := range m.subscribers[topic] { for _, sb := range b.subscribers[topic] {
if sb.id == sub.id { if sb.id == sub.id {
continue continue
} }
newSubscribers = append(newSubscribers, sb) newSubscribers = append(newSubscribers, sb)
} }
m.subscribers[topic] = newSubscribers b.subscribers[topic] = newSubscribers
m.Unlock() b.Unlock()
}() }()
return sub, nil return sub, nil
} }
func (m *Broker) String() string { func (b *Broker) String() string {
return "memory" return "memory"
} }
func (m *Broker) Name() string { func (b *Broker) Name() string {
return m.opts.Name return b.opts.Name
} }
func (m *Broker) Live() bool { func (b *Broker) Live() bool {
return true return true
} }
func (m *Broker) Ready() bool { func (b *Broker) Ready() bool {
return true return true
} }
func (m *Broker) Health() bool { func (b *Broker) Health() bool {
return true return true
} }
func (m *memoryMessage) Topic() string {
return m.topic
}
func (m *memoryMessage) Body() []byte {
return m.body
}
func (m *memoryMessage) Ack() error {
return nil
}
func (m *memoryMessage) Context() context.Context {
return m.ctx
}
func (m *Subscriber) Options() broker.SubscribeOptions { func (m *Subscriber) Options() broker.SubscribeOptions {
return m.opts return m.opts
} }

View File

@ -5,12 +5,23 @@ import (
"fmt" "fmt"
"testing" "testing"
"go.uber.org/atomic"
"go.unistack.org/micro/v4/broker" "go.unistack.org/micro/v4/broker"
"go.unistack.org/micro/v4/codec"
"go.unistack.org/micro/v4/metadata" "go.unistack.org/micro/v4/metadata"
) )
type hldr struct {
c atomic.Int64
}
func (h *hldr) Handler(m broker.Message) error {
h.c.Add(1)
return nil
}
func TestMemoryBroker(t *testing.T) { func TestMemoryBroker(t *testing.T) {
b := NewBroker() b := NewBroker(broker.Codec("application/octet-stream", codec.NewCodec()))
ctx := context.Background() ctx := context.Background()
if err := b.Init(); err != nil { if err := b.Init(); err != nil {
@ -22,28 +33,27 @@ func TestMemoryBroker(t *testing.T) {
} }
topic := "test" topic := "test"
count := 10 count := int64(10)
fn := func(_ broker.Message) error { h := &hldr{}
return nil
}
sub, err := b.Subscribe(ctx, topic, fn) sub, err := b.Subscribe(ctx, topic, h.Handler)
if err != nil { if err != nil {
t.Fatalf("Unexpected error subscribing %v", err) t.Fatalf("Unexpected error subscribing %v", err)
} }
msgs := make([]*broker.Message, 0, count) for i := int64(0); i < count; i++ {
for i := 0; i < count; i++ { message, err := b.NewMessage(ctx,
message, err := b.NewMessage(ctx, metadata.Pairs() metadata.Pairs(
Header: map[string]string{ "foo", "bar",
metadata.HeaderTopic: topic, "id", fmt.Sprintf("%d", i),
"foo": "bar", ),
"id": fmt.Sprintf("%d", i),
},
[]byte(`"hello world"`), []byte(`"hello world"`),
broker.PublishContentType("application/octet-stream"),
)
if err != nil {
t.Fatal(err)
} }
msgs = append(msgs, message)
if err := b.Publish(ctx, topic, message); err != nil { if err := b.Publish(ctx, topic, message); err != nil {
t.Fatalf("Unexpected error publishing %d err: %v", i, err) t.Fatalf("Unexpected error publishing %d err: %v", i, err)
@ -57,4 +67,8 @@ func TestMemoryBroker(t *testing.T) {
if err := b.Disconnect(ctx); err != nil { if err := b.Disconnect(ctx); err != nil {
t.Fatalf("Unexpected connect error %v", err) t.Fatalf("Unexpected connect error %v", err)
} }
if h.c.Load() != count {
t.Fatal("invalid messages count received")
}
} }

View File

@ -118,10 +118,6 @@ func (m *noopMessage) Context() context.Context {
return m.ctx return m.ctx
} }
func (m *noopMessage) Error() error {
return nil
}
func (m *noopMessage) Topic() string { func (m *noopMessage) Topic() string {
return "" return ""
} }

View File

@ -133,8 +133,8 @@ func Addrs(addrs ...string) Option {
} }
} }
// Codecs sets the codec used for encoding/decoding messages // Codec sets the codec used for encoding/decoding messages
func Codecs(ct string, c codec.Codec) Option { func Codec(ct string, c codec.Codec) Option {
return func(o *Options) { return func(o *Options) {
o.Codecs[ct] = c o.Codecs[ct] = c
} }

View File

@ -1,6 +1,4 @@
//go:build ignore package broker
package server
import ( import (
"fmt" "fmt"
@ -10,7 +8,8 @@ import (
) )
const ( const (
subSig = "func(context.Context, interface{}) error" messageSig = "func(broker.Message) error"
messagesSig = "func([]broker.Message) error"
) )
// Precompute the reflect type for error. Can't use error directly // Precompute the reflect type for error. Can't use error directly
@ -33,31 +32,31 @@ func isExportedOrBuiltinType(t reflect.Type) bool {
return isExported(t.Name()) || t.PkgPath() == "" return isExported(t.Name()) || t.PkgPath() == ""
} }
// ValidateSubscriber func signature // IsValidHandler func signature
func ValidateSubscriber(sub Subscriber) error { func IsValidHandler(sub interface{}) error {
typ := reflect.TypeOf(sub.Subscriber()) typ := reflect.TypeOf(sub)
var argType reflect.Type var argType reflect.Type
switch typ.Kind() { switch typ.Kind() {
case reflect.Func: case reflect.Func:
name := "Func" name := "Func"
switch typ.NumIn() { switch typ.NumIn() {
case 2: case 1:
argType = typ.In(1) argType = typ.In(0)
default: default:
return fmt.Errorf("subscriber %v takes wrong number of args: %v required signature %s", name, typ.NumIn(), subSig) return fmt.Errorf("subscriber %v takes wrong number of args: %v required signature %s", name, typ.NumIn(), messageSig)
} }
if !isExportedOrBuiltinType(argType) { if !isExportedOrBuiltinType(argType) {
return fmt.Errorf("subscriber %v argument type not exported: %v", name, argType) return fmt.Errorf("subscriber %v argument type not exported: %v", name, argType)
} }
if typ.NumOut() != 1 { if typ.NumOut() != 1 {
return fmt.Errorf("subscriber %v has wrong number of return values: %v require signature %s", return fmt.Errorf("subscriber %v has wrong number of return values: %v require signature %s",
name, typ.NumOut(), subSig) name, typ.NumOut(), messageSig)
} }
if returnType := typ.Out(0); returnType != typeOfError { if returnType := typ.Out(0); returnType != typeOfError {
return fmt.Errorf("subscriber %v returns %v not error", name, returnType.String()) return fmt.Errorf("subscriber %v returns %v not error", name, returnType.String())
} }
default: default:
hdlr := reflect.ValueOf(sub.Subscriber()) hdlr := reflect.ValueOf(sub)
name := reflect.Indirect(hdlr).Type().Name() name := reflect.Indirect(hdlr).Type().Name()
for m := 0; m < typ.NumMethod(); m++ { for m := 0; m < typ.NumMethod(); m++ {
@ -67,7 +66,7 @@ func ValidateSubscriber(sub Subscriber) error {
argType = method.Type.In(2) argType = method.Type.In(2)
default: default:
return fmt.Errorf("subscriber %v.%v takes wrong number of args: %v required signature %s", return fmt.Errorf("subscriber %v.%v takes wrong number of args: %v required signature %s",
name, method.Name, method.Type.NumIn(), subSig) name, method.Name, method.Type.NumIn(), messageSig)
} }
if !isExportedOrBuiltinType(argType) { if !isExportedOrBuiltinType(argType) {
@ -76,7 +75,7 @@ func ValidateSubscriber(sub Subscriber) error {
if method.Type.NumOut() != 1 { if method.Type.NumOut() != 1 {
return fmt.Errorf( return fmt.Errorf(
"subscriber %v.%v has wrong number of return values: %v require signature %s", "subscriber %v.%v has wrong number of return values: %v require signature %s",
name, method.Name, method.Type.NumOut(), subSig) name, method.Name, method.Type.NumOut(), messageSig)
} }
if returnType := method.Type.Out(0); returnType != typeOfError { if returnType := method.Type.Out(0); returnType != typeOfError {
return fmt.Errorf("subscriber %v.%v returns %v not error", name, method.Name, returnType.String()) return fmt.Errorf("subscriber %v.%v returns %v not error", name, method.Name, returnType.String())

View File

@ -412,9 +412,10 @@ func Test_WithContextAttrFunc(t *testing.T) {
} }
attrs := make([]interface{}, 0, 10) attrs := make([]interface{}, 0, 10)
for k, v := range md { for k, v := range md {
switch k { key := strings.ToLower(k)
case "X-Request-Id", "Phone", "External-Id", "Source-Service", "X-App-Install-Id", "Client-Id", "Client-Ip": switch key {
attrs = append(attrs, strings.ToLower(k), v) case "x-request-id", "phone", "external-Id", "source-service", "x-app-install-id", "client-id", "client-ip":
attrs = append(attrs, key, v[0])
} }
} }
return attrs return attrs

View File

@ -1,181 +0,0 @@
//go:build !exclude
// Package metadata is a way of defining message headers
package metadata
import (
"context"
)
type (
mdIncomingKey struct{}
mdOutgoingKey struct{}
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 || md.md == nil {
return nil, false
}
return md.md, ok
}
// MustIncomingContext returns metadata from incoming ctx
// returned metadata shoud not be modified or race condition happens.
// If metadata not exists panics.
func MustIncomingContext(ctx context.Context) Metadata {
md, ok := FromIncomingContext(ctx)
if !ok {
panic("missing metadata")
}
return md
}
// 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 || md.md == nil {
return nil, false
}
return md.md, ok
}
// MustOutgoingContext returns metadata from outgoing ctx
// returned metadata shoud not be modified or race condition happens.
// If metadata not exists panics.
func MustOutgoingContext(ctx context.Context) Metadata {
md, ok := FromOutgoingContext(ctx)
if !ok {
panic("missing metadata")
}
return md
}
// FromContext returns metadata from the given context
// returned metadata shoud not be modified or race condition happens
func FromContext(ctx context.Context) (Metadata, bool) {
if ctx == nil {
return nil, false
}
md, ok := ctx.Value(mdKey{}).(*rawMetadata)
if !ok || md.md == nil {
return nil, false
}
return md.md, ok
}
// MustContext returns metadata from the given context
// returned metadata shoud not be modified or race condition happens
func MustContext(ctx context.Context) Metadata {
md, ok := FromContext(ctx)
if !ok {
panic("missing metadata")
}
return md
}
// NewContext creates a new context with the given metadata
func NewContext(ctx context.Context, md Metadata) context.Context {
if ctx == nil {
ctx = context.Background()
}
return context.WithValue(ctx, mdKey{}, &rawMetadata{md})
}
// 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()
}
return context.WithValue(ctx, mdIncomingKey{}, &rawMetadata{md})
}
// NewOutgoingContext creates a new context with outcoming metadata attached
func NewOutgoingContext(ctx context.Context, md Metadata) context.Context {
if ctx == nil {
ctx = context.Background()
}
return context.WithValue(ctx, mdOutgoingKey{}, &rawMetadata{md})
}
// AppendOutgoingContext apends new md to context
func AppendOutgoingContext(ctx context.Context, kv ...string) context.Context {
md, ok := Pairs(kv...)
if !ok {
return ctx
}
omd, ok := FromOutgoingContext(ctx)
if !ok {
return NewOutgoingContext(ctx, md)
}
for k, v := range md {
omd.Set(k, v)
}
return ctx
}
// AppendIncomingContext apends new md to context
func AppendIncomingContext(ctx context.Context, kv ...string) context.Context {
md, ok := Pairs(kv...)
if !ok {
return ctx
}
omd, ok := FromIncomingContext(ctx)
if !ok {
return NewIncomingContext(ctx, md)
}
for k, v := range md {
omd.Set(k, v)
}
return ctx
}
// AppendContext apends new md to context
func AppendContext(ctx context.Context, kv ...string) context.Context {
md, ok := Pairs(kv...)
if !ok {
return ctx
}
omd, ok := FromContext(ctx)
if !ok {
return NewContext(ctx, md)
}
for k, v := range md {
omd.Set(k, v)
}
return ctx
}

View File

@ -1,140 +0,0 @@
package metadata
import (
"context"
"testing"
)
func TestFromNilContext(t *testing.T) {
// nolint: staticcheck
c, ok := FromContext(nil)
if ok || c != nil {
t.Fatal("FromContext not works")
}
}
func TestNewNilContext(t *testing.T) {
// nolint: staticcheck
ctx := NewContext(nil, New(0))
c, ok := FromContext(ctx)
if c == nil || !ok {
t.Fatal("NewContext not works")
}
}
func TestFromContext(t *testing.T) {
ctx := context.WithValue(context.TODO(), mdKey{}, &rawMetadata{New(0)})
c, ok := FromContext(ctx)
if c == nil || !ok {
t.Fatal("FromContext not works")
}
}
func TestNewContext(t *testing.T) {
ctx := NewContext(context.TODO(), New(0))
c, ok := FromContext(ctx)
if c == nil || !ok {
t.Fatal("NewContext not works")
}
}
func TestFromIncomingContext(t *testing.T) {
ctx := context.WithValue(context.TODO(), mdIncomingKey{}, &rawMetadata{New(0)})
c, ok := FromIncomingContext(ctx)
if c == nil || !ok {
t.Fatal("FromIncomingContext not works")
}
}
func TestFromOutgoingContext(t *testing.T) {
ctx := context.WithValue(context.TODO(), mdOutgoingKey{}, &rawMetadata{New(0)})
c, ok := FromOutgoingContext(ctx)
if c == nil || !ok {
t.Fatal("FromOutgoingContext not works")
}
}
func TestSetIncomingContext(t *testing.T) {
md := New(1)
md.Set("key", "val")
ctx := context.WithValue(context.TODO(), mdIncomingKey{}, &rawMetadata{})
if !SetIncomingContext(ctx, md) {
t.Fatal("SetIncomingContext not works")
}
md, ok := FromIncomingContext(ctx)
if md == nil || !ok {
t.Fatal("SetIncomingContext not works")
} else if v, ok := md.Get("key"); !ok || v != "val" {
t.Fatal("SetIncomingContext not works")
}
}
func TestSetOutgoingContext(t *testing.T) {
md := New(1)
md.Set("key", "val")
ctx := context.WithValue(context.TODO(), mdOutgoingKey{}, &rawMetadata{})
if !SetOutgoingContext(ctx, md) {
t.Fatal("SetOutgoingContext not works")
}
md, ok := FromOutgoingContext(ctx)
if md == nil || !ok {
t.Fatal("SetOutgoingContext not works")
} else if v, ok := md.Get("key"); !ok || v != "val" {
t.Fatal("SetOutgoingContext not works")
}
}
func TestNewIncomingContext(t *testing.T) {
md := New(1)
md.Set("key", "val")
ctx := NewIncomingContext(context.TODO(), md)
c, ok := FromIncomingContext(ctx)
if c == nil || !ok {
t.Fatal("NewIncomingContext not works")
}
}
func TestNewOutgoingContext(t *testing.T) {
md := New(1)
md.Set("key", "val")
ctx := NewOutgoingContext(context.TODO(), md)
c, ok := FromOutgoingContext(ctx)
if c == nil || !ok {
t.Fatal("NewOutgoingContext not works")
}
}
func TestAppendIncomingContext(t *testing.T) {
md := New(1)
md.Set("key1", "val1")
ctx := AppendIncomingContext(context.TODO(), "key2", "val2")
nmd, ok := FromIncomingContext(ctx)
if nmd == nil || !ok {
t.Fatal("AppendIncomingContext not works")
}
if v, ok := nmd.Get("key2"); !ok || v != "val2" {
t.Fatal("AppendIncomingContext not works")
}
}
func TestAppendOutgoingContext(t *testing.T) {
md := New(1)
md.Set("key1", "val1")
ctx := AppendOutgoingContext(context.TODO(), "key2", "val2")
nmd, ok := FromOutgoingContext(ctx)
if nmd == nil || !ok {
t.Fatal("AppendOutgoingContext not works")
}
if v, ok := nmd.Get("key2"); !ok || v != "val2" {
t.Fatal("AppendOutgoingContext not works")
}
}

19
metadata/headers.go Normal file
View File

@ -0,0 +1,19 @@
// Package metadata is a way of defining message headers
package metadata
var (
// HeaderTopic is the header name that contains topic name
HeaderTopic = "Micro-Topic"
// HeaderContentType specifies content type of message
HeaderContentType = "Content-Type"
// HeaderEndpoint specifies endpoint in service
HeaderEndpoint = "Micro-Endpoint"
// HeaderService specifies service
HeaderService = "Micro-Service"
// HeaderTimeout specifies timeout of operation
HeaderTimeout = "Micro-Timeout"
// HeaderAuthorization specifies Authorization header
HeaderAuthorization = "Authorization"
// HeaderXRequestID specifies request id
HeaderXRequestID = "X-Request-Id"
)

View File

@ -1,43 +1,461 @@
//go:build !exclude
// Package metadata is a way of defining message headers
package metadata package metadata
import ( import (
"context"
"fmt"
"net/textproto" "net/textproto"
"sort"
"strings" "strings"
) )
var (
// HeaderTopic is the header name that contains topic name
HeaderTopic = "Micro-Topic"
// HeaderContentType specifies content type of message
HeaderContentType = "Content-Type"
// HeaderEndpoint specifies endpoint in service
HeaderEndpoint = "Micro-Endpoint"
// HeaderService specifies service
HeaderService = "Micro-Service"
// HeaderTimeout specifies timeout of operation
HeaderTimeout = "Micro-Timeout"
// HeaderAuthorization specifies Authorization header
HeaderAuthorization = "Authorization"
// HeaderXRequestID specifies request id
HeaderXRequestID = "X-Request-Id"
)
// 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
}
// defaultMetadataSize used when need to init new Metadata // defaultMetadataSize used when need to init new Metadata
var defaultMetadataSize = 2 var defaultMetadataSize = 2
// Metadata is a mapping from metadata keys to values. Users should use the following
// two convenience functions New and Pairs to generate Metadata.
type Metadata map[string][]string
// New creates an zero Metadata.
func New(l int) Metadata {
if l == 0 {
l = defaultMetadataSize
}
md := make(Metadata, l)
return md
}
// NewWithMetadata creates an Metadata from a given key-value map.
func NewWithMetadata(m map[string]string) Metadata {
md := make(Metadata, len(m))
for key, val := range m {
md[key] = append(md[key], val)
}
return md
}
// Pairs returns an Metadata formed by the mapping of key, value ...
// Pairs panics if len(kv) is odd.
func Pairs(kv ...string) Metadata {
if len(kv)%2 == 1 {
panic(fmt.Sprintf("metadata: Pairs got the odd number of input pairs for metadata: %d", len(kv)))
}
md := make(Metadata, len(kv)/2)
for i := 0; i < len(kv); i += 2 {
md[kv[i]] = append(md[kv[i]], kv[i+1])
}
return md
}
// Len returns the number of items in Metadata.
func (md Metadata) Len() int {
return len(md)
}
// Copy returns a copy of Metadata.
func Copy(src Metadata) Metadata {
out := make(Metadata, len(src))
for k, v := range src {
out[k] = copyOf(v)
}
return out
}
// Copy returns a copy of Metadata.
func (md Metadata) Copy() Metadata {
out := make(Metadata, len(md))
for k, v := range md {
out[k] = copyOf(v)
}
return out
}
// AsHTTP1 returns a copy of Metadata
// with CanonicalMIMEHeaderKey.
func (md Metadata) AsHTTP1() map[string][]string {
out := make(map[string][]string, len(md))
for k, v := range md {
out[textproto.CanonicalMIMEHeaderKey(k)] = copyOf(v)
}
return out
}
// AsHTTP1 returns a copy of Metadata
// with strings.ToLower.
func (md Metadata) AsHTTP2() map[string][]string {
out := make(map[string][]string, len(md))
for k, v := range md {
out[strings.ToLower(k)] = copyOf(v)
}
return out
}
// CopyTo copies Metadata to out.
func (md Metadata) CopyTo(out Metadata) {
for k, v := range md {
out[k] = copyOf(v)
}
}
// Get obtains the values for a given key.
func (md Metadata) MustGet(k string) []string {
v, ok := md.Get(k)
if !ok {
panic("missing metadata key")
}
return v
}
// Get obtains the values for a given key.
func (md Metadata) Get(k string) ([]string, bool) {
v, ok := md[k]
if !ok {
v, ok = md[strings.ToLower(k)]
}
if !ok {
v, ok = md[textproto.CanonicalMIMEHeaderKey(k)]
}
return v, ok
}
// MustGetJoined obtains the values for a given key
// with joined values with "," symbol
func (md Metadata) MustGetJoined(k string) string {
v, ok := md.GetJoined(k)
if !ok {
panic("missing metadata key")
}
return v
}
// GetJoined obtains the values for a given key
// with joined values with "," symbol
func (md Metadata) GetJoined(k string) (string, bool) {
v, ok := md.Get(k)
if !ok {
return "", ok
}
return strings.Join(v, ","), true
}
// Set sets the value of a given key with a slice of values.
func (md Metadata) Add(key string, vals ...string) {
if len(vals) == 0 {
return
}
md[key] = vals
}
// Set sets the value of a given key with a slice of values.
func (md Metadata) Set(kvs ...string) {
if len(kvs)%2 == 1 {
panic(fmt.Sprintf("metadata: Set got an odd number of input pairs for metadata: %d", len(kvs)))
}
for i := 0; i < len(kvs); i += 2 {
md[kvs[i]] = append(md[kvs[i]], kvs[i+1])
}
}
// Append adds the values to key k, not overwriting what was already stored at
// that key.
func (md Metadata) Append(key string, vals ...string) {
if len(vals) == 0 {
return
}
md[key] = append(md[key], vals...)
}
// Del removes the values for a given keys k.
func (md Metadata) Del(k ...string) {
for i := range k {
delete(md, k[i])
delete(md, strings.ToLower(k[i]))
delete(md, textproto.CanonicalMIMEHeaderKey(k[i]))
}
}
// Join joins any number of Metadatas into a single Metadata.
//
// The order of values for each key is determined by the order in which the Metadatas
// containing those values are presented to Join.
func Join(mds ...Metadata) Metadata {
out := Metadata{}
for _, Metadata := range mds {
for k, v := range Metadata {
out[k] = append(out[k], v...)
}
}
return out
}
type (
metadataIncomingKey struct{}
metadataOutgoingKey struct{}
metadataCurrentKey struct{}
)
// NewContext creates a new context with Metadata attached. Metadata must
// not be modified after calling this function.
func NewContext(ctx context.Context, md Metadata) context.Context {
return context.WithValue(ctx, metadataCurrentKey{}, rawMetadata{md: md})
}
// NewIncomingContext creates a new context with incoming Metadata attached. Metadata must
// not be modified after calling this function.
func NewIncomingContext(ctx context.Context, md Metadata) context.Context {
return context.WithValue(ctx, metadataIncomingKey{}, rawMetadata{md: md})
}
// NewOutgoingContext creates a new context with outgoing Metadata attached. If used
// in conjunction with AppendOutgoingContext, NewOutgoingContext will
// overwrite any previously-appended metadata. Metadata must not be modified after
// calling this function.
func NewOutgoingContext(ctx context.Context, md Metadata) context.Context {
return context.WithValue(ctx, metadataOutgoingKey{}, rawMetadata{md: md})
}
// AppendContext returns a new context with the provided kv merged
// with any existing metadata in the context. Please refer to the documentation
// of Pairs for a description of kv.
func AppendContext(ctx context.Context, kv ...string) context.Context {
if len(kv)%2 == 1 {
panic(fmt.Sprintf("metadata: AppendContext got an odd number of input pairs for metadata: %d", len(kv)))
}
md, _ := ctx.Value(metadataCurrentKey{}).(rawMetadata)
added := make([][]string, len(md.added)+1)
copy(added, md.added)
kvCopy := make([]string, 0, len(kv))
for i := 0; i < len(kv); i += 2 {
kvCopy = append(kvCopy, strings.ToLower(kv[i]), kv[i+1])
}
added[len(added)-1] = kvCopy
return context.WithValue(ctx, metadataCurrentKey{}, rawMetadata{md: md.md, added: added})
}
// AppendIncomingContext returns a new context with the provided kv merged
// with any existing metadata in the context. Please refer to the documentation
// of Pairs for a description of kv.
func AppendIncomingContext(ctx context.Context, kv ...string) context.Context {
if len(kv)%2 == 1 {
panic(fmt.Sprintf("metadata: AppendIncomingContext got an odd number of input pairs for metadata: %d", len(kv)))
}
md, _ := ctx.Value(metadataIncomingKey{}).(rawMetadata)
added := make([][]string, len(md.added)+1)
copy(added, md.added)
kvCopy := make([]string, 0, len(kv))
for i := 0; i < len(kv); i += 2 {
kvCopy = append(kvCopy, strings.ToLower(kv[i]), kv[i+1])
}
added[len(added)-1] = kvCopy
return context.WithValue(ctx, metadataIncomingKey{}, rawMetadata{md: md.md, added: added})
}
// AppendOutgoingContext returns a new context with the provided kv merged
// with any existing metadata in the context. Please refer to the documentation
// of Pairs for a description of kv.
func AppendOutgoingContext(ctx context.Context, kv ...string) context.Context {
if len(kv)%2 == 1 {
panic(fmt.Sprintf("metadata: AppendOutgoingContext got an odd number of input pairs for metadata: %d", len(kv)))
}
md, _ := ctx.Value(metadataOutgoingKey{}).(rawMetadata)
added := make([][]string, len(md.added)+1)
copy(added, md.added)
kvCopy := make([]string, 0, len(kv))
for i := 0; i < len(kv); i += 2 {
kvCopy = append(kvCopy, strings.ToLower(kv[i]), kv[i+1])
}
added[len(added)-1] = kvCopy
return context.WithValue(ctx, metadataOutgoingKey{}, rawMetadata{md: md.md, added: added})
}
// FromContext returns the metadata in ctx if it exists.
func FromContext(ctx context.Context) (Metadata, bool) {
raw, ok := ctx.Value(metadataCurrentKey{}).(rawMetadata)
if !ok {
return nil, false
}
metadataSize := len(raw.md)
for i := range raw.added {
metadataSize += len(raw.added[i]) / 2
}
out := make(Metadata, metadataSize)
for k, v := range raw.md {
out[k] = copyOf(v)
}
for _, added := range raw.added {
if len(added)%2 == 1 {
panic(fmt.Sprintf("metadata: FromContext got an odd number of input pairs for metadata: %d", len(added)))
}
for i := 0; i < len(added); i += 2 {
out[added[i]] = append(out[added[i]], added[i+1])
}
}
return out, true
}
// MustContext returns the metadata in ctx.
func MustContext(ctx context.Context) Metadata {
md, ok := FromContext(ctx)
if !ok {
panic("missing metadata")
}
return md
}
// FromIncomingContext returns the incoming metadata in ctx if it exists.
func FromIncomingContext(ctx context.Context) (Metadata, bool) {
raw, ok := ctx.Value(metadataIncomingKey{}).(rawMetadata)
if !ok {
return nil, false
}
metadataSize := len(raw.md)
for i := range raw.added {
metadataSize += len(raw.added[i]) / 2
}
out := make(Metadata, metadataSize)
for k, v := range raw.md {
out[k] = copyOf(v)
}
for _, added := range raw.added {
if len(added)%2 == 1 {
panic(fmt.Sprintf("metadata: FromIncomingContext got an odd number of input pairs for metadata: %d", len(added)))
}
for i := 0; i < len(added); i += 2 {
out[added[i]] = append(out[added[i]], added[i+1])
}
}
return out, true
}
// MustIncomingContext returns the incoming metadata in ctx.
func MustIncomingContext(ctx context.Context) Metadata {
md, ok := FromIncomingContext(ctx)
if !ok {
panic("missing metadata")
}
return md
}
// ValueFromIncomingContext returns the metadata value corresponding to the metadata
// key from the incoming metadata if it exists. Keys are matched in a case insensitive
// manner.
func ValueFromIncomingContext(ctx context.Context, key string) []string {
raw, ok := ctx.Value(metadataIncomingKey{}).(rawMetadata)
if !ok {
return nil
}
if v, ok := raw.md[key]; ok {
return copyOf(v)
}
for k, v := range raw.md {
// Case insensitive comparison: Metadata is a map, and there's no guarantee
// that the Metadata attached to the context is created using our helper
// functions.
if strings.EqualFold(k, key) {
return copyOf(v)
}
}
return nil
}
// ValueFromCurrentContext returns the metadata value corresponding to the metadata
// key from the incoming metadata if it exists. Keys are matched in a case insensitive
// manner.
func ValueFromCurrentContext(ctx context.Context, key string) []string {
md, ok := ctx.Value(metadataCurrentKey{}).(rawMetadata)
if !ok {
return nil
}
if v, ok := md.md[key]; ok {
return copyOf(v)
}
for k, v := range md.md {
// Case insensitive comparison: Metadata is a map, and there's no guarantee
// that the Metadata attached to the context is created using our helper
// functions.
if strings.EqualFold(k, key) {
return copyOf(v)
}
}
return nil
}
// MustOutgoingContext returns the outgoing metadata in ctx.
func MustOutgoingContext(ctx context.Context) Metadata {
md, ok := FromOutgoingContext(ctx)
if !ok {
panic("missing metadata")
}
return md
}
// ValueFromOutgoingContext returns the metadata value corresponding to the metadata
// key from the incoming metadata if it exists. Keys are matched in a case insensitive
// manner.
func ValueFromOutgoingContext(ctx context.Context, key string) []string {
md, ok := ctx.Value(metadataOutgoingKey{}).(rawMetadata)
if !ok {
return nil
}
if v, ok := md.md[key]; ok {
return copyOf(v)
}
for k, v := range md.md {
// Case insensitive comparison: Metadata is a map, and there's no guarantee
// that the Metadata attached to the context is created using our helper
// functions.
if strings.EqualFold(k, key) {
return copyOf(v)
}
}
return nil
}
func copyOf(v []string) []string {
vals := make([]string, len(v))
copy(vals, v)
return vals
}
// FromOutgoingContext returns the outgoing metadata in ctx if it exists.
func FromOutgoingContext(ctx context.Context) (Metadata, bool) {
raw, ok := ctx.Value(metadataOutgoingKey{}).(rawMetadata)
if !ok {
return nil, false
}
metadataSize := len(raw.md)
for i := range raw.added {
metadataSize += len(raw.added[i]) / 2
}
out := make(Metadata, metadataSize)
for k, v := range raw.md {
out[k] = copyOf(v)
}
for _, added := range raw.added {
if len(added)%2 == 1 {
panic(fmt.Sprintf("metadata: FromOutgoingContext got an odd number of input pairs for metadata: %d", len(added)))
}
for i := 0; i < len(added); i += 2 {
out[added[i]] = append(out[added[i]], added[i+1])
}
}
return out, ok
}
type rawMetadata struct {
md Metadata
added [][]string
}
// Iterator used to iterate over metadata with order // Iterator used to iterate over metadata with order
type Iterator struct { type Iterator struct {
md Metadata md Metadata
@ -46,6 +464,7 @@ type Iterator struct {
cnt int cnt int
} }
/*
// Next advance iterator to next element // Next advance iterator to next element
func (iter *Iterator) Next(k, v *string) bool { func (iter *Iterator) Next(k, v *string) bool {
if iter.cur+1 > iter.cnt { if iter.cur+1 > iter.cnt {
@ -53,122 +472,19 @@ func (iter *Iterator) Next(k, v *string) bool {
} }
*k = iter.keys[iter.cur] *k = iter.keys[iter.cur]
*v = iter.md[*k] *v = iter.Metadata[*k]
iter.cur++ iter.cur++
return true return true
} }
// Iterator returns the itarator for metadata in sorted order // Iterator returns the itarator for metadata in sorted order
func (md Metadata) Iterator() *Iterator { func (Metadata Metadata) Iterator() *Iterator {
iter := &Iterator{md: md, cnt: len(md)} iter := &Iterator{Metadata: Metadata, cnt: len(Metadata)}
iter.keys = make([]string, 0, iter.cnt) iter.keys = make([]string, 0, iter.cnt)
for k := range md { for k := range Metadata {
iter.keys = append(iter.keys, k) iter.keys = append(iter.keys, k)
} }
sort.Strings(iter.keys) sort.Strings(iter.keys)
return iter return iter
} }
*/
func (md Metadata) MustGet(key string) string {
val, ok := md.Get(key)
if !ok {
panic("missing metadata key")
}
return val
}
// Len returns the number of items.
func (md Metadata) Len() int {
return len(md)
}
// Get returns value from metadata by key
func (md Metadata) Get(key string) (string, bool) {
// fast path
val, ok := md[key]
if !ok {
// slow path
val, ok = md[textproto.CanonicalMIMEHeaderKey(key)]
if !ok {
val, ok = md[strings.ToLower(key)]
}
}
return val, ok
}
// Set is used to store value in metadata
func (md Metadata) Set(kv ...string) {
if len(kv)%2 == 1 {
kv = kv[:len(kv)-1]
}
for idx := 0; idx < len(kv); idx += 2 {
md[textproto.CanonicalMIMEHeaderKey(kv[idx])] = kv[idx+1]
}
}
// Del is used to remove value from metadata
func (md Metadata) Del(keys ...string) {
for _, key := range keys {
// fast path
delete(md, key)
// slow path
delete(md, textproto.CanonicalMIMEHeaderKey(key))
// very slow path
delete(md, strings.ToLower(key))
}
}
// Copy makes a copy of the metadata
func (md Metadata) CopyTo(dst Metadata) {
for k, v := range md {
dst[k] = v
}
}
// Copy makes a copy of the metadata
func Copy(md Metadata, exclude ...string) Metadata {
nmd := New(len(md))
for k, v := range md {
nmd[k] = v
}
nmd.Del(exclude...)
return nmd
}
// New return new sized metadata
func New(size int) Metadata {
if size == 0 {
size = defaultMetadataSize
}
return make(Metadata, size)
}
// Merge merges metadata to existing metadata, overwriting if specified
func Merge(omd Metadata, mmd Metadata, overwrite bool) Metadata {
var ok bool
nmd := Copy(omd)
for key, val := range mmd {
_, ok = nmd[key]
switch {
case ok && !overwrite:
continue
case val != "":
nmd[key] = val
case ok && val == "":
nmd.Del(key)
}
}
return nmd
}
// Pairs from which metadata created
func Pairs(kv ...string) Metadata {
if len(kv)%2 == 1 {
return nil
}
md := New(len(kv) / 2)
for idx := 0; idx < len(kv); idx += 2 {
md[kv[idx]] = kv[idx+1]
}
return md
}

View File

@ -5,10 +5,21 @@ import (
"testing" "testing"
) )
/*
func TestAppendOutgoingContextModify(t *testing.T) {
md := Pairs("key1", "val1")
ctx := NewOutgoingContext(context.TODO(), md)
nctx := AppendOutgoingContext(ctx, "key1", "val3", "key2", "val2")
_ = nctx
omd := MustOutgoingContext(nctx)
fmt.Printf("%#+v\n", omd)
}
*/
func TestLowercase(t *testing.T) { func TestLowercase(t *testing.T) {
md := New(1) md := New(1)
md["x-request-id"] = "12345" md["x-request-id"] = []string{"12345"}
v, ok := md.Get("X-Request-Id") v, ok := md.GetJoined("X-Request-Id")
if !ok || v == "" { if !ok || v == "" {
t.Fatalf("metadata invalid %#+v", md) t.Fatalf("metadata invalid %#+v", md)
} }
@ -38,15 +49,12 @@ func TestMultipleUsage(t *testing.T) {
func TestMetadataSetMultiple(t *testing.T) { func TestMetadataSetMultiple(t *testing.T) {
md := New(4) md := New(4)
md.Set("key1", "val1", "key2", "val2", "key3") md.Set("key1", "val1", "key2", "val2")
if v, ok := md.Get("key1"); !ok || v != "val1" { if v, ok := md.GetJoined("key1"); !ok || v != "val1" {
t.Fatalf("invalid kv %#+v", md) t.Fatalf("invalid kv %#+v", md)
} }
if v, ok := md.Get("key2"); !ok || v != "val2" { if v, ok := md.GetJoined("key2"); !ok || v != "val2" {
t.Fatalf("invalid kv %#+v", md)
}
if _, ok := md.Get("key3"); ok {
t.Fatalf("invalid kv %#+v", md) t.Fatalf("invalid kv %#+v", md)
} }
} }
@ -64,22 +72,12 @@ func TestAppend(t *testing.T) {
} }
func TestPairs(t *testing.T) { func TestPairs(t *testing.T) {
md, ok := Pairs("key1", "val1", "key2", "val2") md := Pairs("key1", "val1", "key2", "val2")
if !ok { if _, ok := md.Get("key1"); !ok {
t.Fatal("odd number of kv")
}
if _, ok = md.Get("key1"); !ok {
t.Fatal("key1 not found") t.Fatal("key1 not found")
} }
} }
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) { func TestPassing(t *testing.T) {
ctx := context.TODO() ctx := context.TODO()
md1 := New(2) md1 := New(2)
@ -87,37 +85,24 @@ func TestPassing(t *testing.T) {
md1.Set("Key2", "Val2") md1.Set("Key2", "Val2")
ctx = NewIncomingContext(ctx, md1) ctx = NewIncomingContext(ctx, md1)
testCtx(ctx)
_, ok := FromOutgoingContext(ctx) _, ok := FromOutgoingContext(ctx)
if ok { if ok {
t.Fatalf("create outgoing context") t.Fatalf("create outgoing context")
} }
ctx = NewOutgoingContext(ctx, New(1)) ctx = NewOutgoingContext(ctx, md1)
testCtx(ctx)
md, ok := FromOutgoingContext(ctx) md, ok := FromOutgoingContext(ctx)
if !ok { if !ok {
t.Fatalf("missing metadata from outgoing context") t.Fatalf("missing metadata from outgoing context")
} }
if v, ok := md.Get("Key1"); !ok || v != "Val1_new" { if v, ok := md.Get("Key1"); !ok || v[0] != "Val1" {
t.Fatalf("invalid metadata value %#+v", md) t.Fatalf("invalid metadata value %#+v", 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(_ *testing.T) { func TestIterator(_ *testing.T) {
md := Metadata{ md := Metadata{
"1Last": "last", "1Last": "last",
@ -132,24 +117,25 @@ func TestIterator(_ *testing.T) {
// fmt.Printf("k: %s, v: %s\n", k, v) // fmt.Printf("k: %s, v: %s\n", k, v)
} }
} }
*/
func TestMedataCanonicalKey(t *testing.T) { func TestMedataCanonicalKey(t *testing.T) {
md := New(1) md := New(1)
md.Set("x-request-id", "12345") md.Set("x-request-id", "12345")
v, ok := md.Get("x-request-id") v, ok := md.GetJoined("x-request-id")
if !ok { if !ok {
t.Fatalf("failed to get x-request-id") t.Fatalf("failed to get x-request-id")
} else if v != "12345" { } else if v != "12345" {
t.Fatalf("invalid metadata value: %s != %s", "12345", v) t.Fatalf("invalid metadata value: %s != %s", "12345", v)
} }
v, ok = md.Get("X-Request-Id") v, ok = md.GetJoined("X-Request-Id")
if !ok { if !ok {
t.Fatalf("failed to get x-request-id") t.Fatalf("failed to get x-request-id")
} else if v != "12345" { } else if v != "12345" {
t.Fatalf("invalid metadata value: %s != %s", "12345", v) t.Fatalf("invalid metadata value: %s != %s", "12345", v)
} }
v, ok = md.Get("X-Request-ID") v, ok = md.GetJoined("X-Request-ID")
if !ok { if !ok {
t.Fatalf("failed to get x-request-id") t.Fatalf("failed to get x-request-id")
} else if v != "12345" { } else if v != "12345" {
@ -162,7 +148,7 @@ func TestMetadataSet(t *testing.T) {
md.Set("Key", "val") md.Set("Key", "val")
val, ok := md.Get("Key") val, ok := md.GetJoined("Key")
if !ok { if !ok {
t.Fatal("key Key not found") t.Fatal("key Key not found")
} }
@ -173,8 +159,8 @@ func TestMetadataSet(t *testing.T) {
func TestMetadataDelete(t *testing.T) { func TestMetadataDelete(t *testing.T) {
md := Metadata{ md := Metadata{
"Foo": "bar", "Foo": []string{"bar"},
"Baz": "empty", "Baz": []string{"empty"},
} }
md.Del("Baz") md.Del("Baz")
@ -184,25 +170,16 @@ func TestMetadataDelete(t *testing.T) {
} }
} }
func TestNilContext(t *testing.T) {
var ctx context.Context
_, ok := FromContext(ctx)
if ok {
t.Fatal("nil context")
}
}
func TestMetadataCopy(t *testing.T) { func TestMetadataCopy(t *testing.T) {
md := Metadata{ md := Metadata{
"Foo": "bar", "Foo": []string{"bar"},
"Bar": "baz", "Bar": []string{"baz"},
} }
cp := Copy(md) cp := Copy(md)
for k, v := range md { for k, v := range md {
if cv := cp[k]; cv != v { if cv := cp[k]; cv[0] != v[0] {
t.Fatalf("Got %s:%s for %s:%s", k, cv, k, v) t.Fatalf("Got %s:%s for %s:%s", k, cv, k, v)
} }
} }
@ -210,7 +187,7 @@ func TestMetadataCopy(t *testing.T) {
func TestMetadataContext(t *testing.T) { func TestMetadataContext(t *testing.T) {
md := Metadata{ md := Metadata{
"Foo": "bar", "Foo": []string{"bar"},
} }
ctx := NewContext(context.TODO(), md) ctx := NewContext(context.TODO(), md)
@ -220,7 +197,7 @@ func TestMetadataContext(t *testing.T) {
t.Errorf("Unexpected error retrieving metadata, got %t", ok) t.Errorf("Unexpected error retrieving metadata, got %t", ok)
} }
if emd["Foo"] != md["Foo"] { if emd["Foo"][0] != md["Foo"][0] {
t.Errorf("Expected key: %s val: %s, got key: %s val: %s", "Foo", md["Foo"], "Foo", emd["Foo"]) t.Errorf("Expected key: %s val: %s, got key: %s val: %s", "Foo", md["Foo"], "Foo", emd["Foo"])
} }
@ -229,13 +206,88 @@ func TestMetadataContext(t *testing.T) {
} }
} }
func TestCopy(t *testing.T) { func TestFromContext(t *testing.T) {
md := New(2) ctx := context.WithValue(context.TODO(), metadataCurrentKey{}, rawMetadata{md: New(0)})
md.Set("key1", "val1", "key2", "val2")
nmd := Copy(md, "key2") c, ok := FromContext(ctx)
if len(nmd) != 1 { if c == nil || !ok {
t.Fatal("Copy exclude not works") t.Fatal("FromContext not works")
} else if nmd["Key1"] != "val1" { }
t.Fatal("Copy exclude not works") }
func TestNewContext(t *testing.T) {
ctx := NewContext(context.TODO(), New(0))
c, ok := FromContext(ctx)
if c == nil || !ok {
t.Fatal("NewContext not works")
}
}
func TestFromIncomingContext(t *testing.T) {
ctx := context.WithValue(context.TODO(), metadataIncomingKey{}, rawMetadata{md: New(0)})
c, ok := FromIncomingContext(ctx)
if c == nil || !ok {
t.Fatal("FromIncomingContext not works")
}
}
func TestFromOutgoingContext(t *testing.T) {
ctx := context.WithValue(context.TODO(), metadataOutgoingKey{}, rawMetadata{md: New(0)})
c, ok := FromOutgoingContext(ctx)
if c == nil || !ok {
t.Fatal("FromOutgoingContext not works")
}
}
func TestNewIncomingContext(t *testing.T) {
md := New(1)
md.Set("key", "val")
ctx := NewIncomingContext(context.TODO(), md)
c, ok := FromIncomingContext(ctx)
if c == nil || !ok {
t.Fatal("NewIncomingContext not works")
}
}
func TestNewOutgoingContext(t *testing.T) {
md := New(1)
md.Set("key", "val")
ctx := NewOutgoingContext(context.TODO(), md)
c, ok := FromOutgoingContext(ctx)
if c == nil || !ok {
t.Fatal("NewOutgoingContext not works")
}
}
func TestAppendIncomingContext(t *testing.T) {
md := New(1)
md.Set("key1", "val1")
ctx := AppendIncomingContext(context.TODO(), "key2", "val2")
nmd, ok := FromIncomingContext(ctx)
if nmd == nil || !ok {
t.Fatal("AppendIncomingContext not works")
}
if v, ok := nmd.GetJoined("key2"); !ok || v != "val2" {
t.Fatal("AppendIncomingContext not works")
}
}
func TestAppendOutgoingContext(t *testing.T) {
md := New(1)
md.Set("key1", "val1")
ctx := AppendOutgoingContext(context.TODO(), "key2", "val2")
nmd, ok := FromOutgoingContext(ctx)
if nmd == nil || !ok {
t.Fatal("AppendOutgoingContext not works")
}
if v, ok := nmd.GetJoined("key2"); !ok || v != "val2" {
t.Fatal("AppendOutgoingContext not works")
} }
} }

View File

@ -8,6 +8,7 @@ import (
"go.unistack.org/micro/v4/broker" "go.unistack.org/micro/v4/broker"
"go.unistack.org/micro/v4/fsm" "go.unistack.org/micro/v4/fsm"
"go.unistack.org/micro/v4/metadata"
) )
func TestAs(t *testing.T) { func TestAs(t *testing.T) {
@ -61,6 +62,8 @@ func TestAs(t *testing.T) {
} }
} }
var _ broker.Broker = (*bro)(nil)
type bro struct { type bro struct {
name string name string
} }
@ -87,23 +90,18 @@ func (p *bro) Connect(_ context.Context) error { return nil }
// Disconnect disconnect from broker // Disconnect disconnect from broker
func (p *bro) Disconnect(_ context.Context) error { return nil } func (p *bro) Disconnect(_ context.Context) error { return nil }
// Publish message, msg can be single broker.Message or []broker.Message // NewMessage creates new message
func (p *bro) Publish(_ context.Context, _ string, _ *broker.Message, _ ...broker.PublishOption) error { func (p *bro) NewMessage(_ context.Context, _ metadata.Metadata, _ interface{}, _ ...broker.PublishOption) (broker.Message, error) {
return nil
}
// BatchPublish messages to broker with multiple topics
func (p *bro) BatchPublish(_ context.Context, _ []*broker.Message, _ ...broker.PublishOption) error {
return nil
}
// BatchSubscribe subscribes to topic messages via handler
func (p *bro) BatchSubscribe(_ context.Context, _ string, _ broker.BatchHandler, _ ...broker.SubscribeOption) (broker.Subscriber, error) {
return nil, nil return nil, nil
} }
// Publish message, msg can be single broker.Message or []broker.Message
func (p *bro) Publish(_ context.Context, _ string, _ ...broker.Message) error {
return nil
}
// Subscribe subscribes to topic message via handler // Subscribe subscribes to topic message via handler
func (p *bro) Subscribe(_ context.Context, _ string, _ broker.Handler, _ ...broker.SubscribeOption) (broker.Subscriber, error) { func (p *bro) Subscribe(_ context.Context, _ string, _ interface{}, _ ...broker.SubscribeOption) (broker.Subscriber, error) {
return nil, nil return nil, nil
} }

View File

@ -6,9 +6,10 @@ import (
"sync" "sync"
"time" "time"
"go.unistack.org/micro/v4/logger" "go.unistack.org/micro/v3/logger"
"go.unistack.org/micro/v4/register" "go.unistack.org/micro/v3/metadata"
"go.unistack.org/micro/v4/util/id" "go.unistack.org/micro/v3/register"
"go.unistack.org/micro/v3/util/id"
) )
var ( var (
@ -23,10 +24,9 @@ type node struct {
} }
type record struct { type record struct {
Name string Name string
Version string Version string
Metadata map[string]string Nodes map[string]*node
Nodes map[string]*node
} }
type memory struct { type memory struct {
@ -160,19 +160,14 @@ func (m *memory) Register(_ context.Context, s *register.Service, opts ...regist
continue continue
} }
metadata := make(map[string]string, len(n.Metadata)) md := metadata.Copy(n.Metadata)
// make copy of metadata
for k, v := range n.Metadata {
metadata[k] = v
}
// add the node // add the node
srvs[s.Name][s.Version].Nodes[n.ID] = &node{ srvs[s.Name][s.Version].Nodes[n.ID] = &node{
Node: &register.Node{ Node: &register.Node{
ID: n.ID, ID: n.ID,
Address: n.Address, Address: n.Address,
Metadata: metadata, Metadata: md,
}, },
TTL: options.TTL, TTL: options.TTL,
LastSeen: time.Now(), LastSeen: time.Now(),
@ -452,23 +447,15 @@ func serviceToRecord(s *register.Service, ttl time.Duration) *record {
} }
func recordToService(r *record, namespace string) *register.Service { func recordToService(r *record, namespace string) *register.Service {
metadata := make(map[string]string, len(r.Metadata))
for k, v := range r.Metadata {
metadata[k] = v
}
nodes := make([]*register.Node, len(r.Nodes)) nodes := make([]*register.Node, len(r.Nodes))
i := 0 i := 0
for _, n := range r.Nodes { for _, n := range r.Nodes {
md := make(map[string]string, len(n.Metadata)) nmd := metadata.Copy(n.Metadata)
for k, v := range n.Metadata {
md[k] = v
}
nodes[i] = &register.Node{ nodes[i] = &register.Node{
ID: n.ID, ID: n.ID,
Address: n.Address, Address: n.Address,
Metadata: md, Metadata: nmd,
} }
i++ i++
} }

View File

@ -7,7 +7,7 @@ import (
"testing" "testing"
"time" "time"
"go.unistack.org/micro/v4/register" "go.unistack.org/micro/v3/register"
) )
var testData = map[string][]*register.Service{ var testData = map[string][]*register.Service{

View File

@ -77,10 +77,6 @@ func NewRegisterService(s Server) (*register.Service, error) {
} }
node.Metadata = metadata.Copy(opts.Metadata) node.Metadata = metadata.Copy(opts.Metadata)
node.Metadata["server"] = s.String()
node.Metadata["broker"] = opts.Broker.String()
node.Metadata["register"] = opts.Register.String()
return &register.Service{ return &register.Service{
Name: opts.Name, Name: opts.Name,
Version: opts.Version, Version: opts.Version,