diff --git a/metadata/context.go b/metadata/context.go index fadfea5b..51eb3a88 100644 --- a/metadata/context.go +++ b/metadata/context.go @@ -124,7 +124,7 @@ func AppendOutgoingContext(ctx context.Context, kv ...string) context.Context { return NewOutgoingContext(ctx, md) } for k, v := range md { - omd.Set(k, v) + omd[k] = v } return NewOutgoingContext(ctx, omd) } @@ -140,7 +140,7 @@ func AppendIncomingContext(ctx context.Context, kv ...string) context.Context { return NewIncomingContext(ctx, md) } for k, v := range md { - omd.Set(k, v) + omd[k] = v } return NewIncomingContext(ctx, omd) } diff --git a/metadata/metadata.go b/metadata/metadata.go index 5cb23b9b..25791470 100644 --- a/metadata/metadata.go +++ b/metadata/metadata.go @@ -24,7 +24,7 @@ var ( // 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 Metadata map[string][]string type rawMetadata struct { md Metadata @@ -42,7 +42,7 @@ type Iterator struct { } // Next advance iterator to next element -func (iter *Iterator) Next(k, v *string) bool { +func (iter *Iterator) Next(k *string, v *[]string) bool { if iter.cur+1 > iter.cnt { return false } @@ -64,8 +64,11 @@ func (md Metadata) Iterator() *Iterator { return iter } -// Get returns value from metadata by key -func (md Metadata) Get(key string) (string, bool) { +// Values returns values from metadata by key +func (md Metadata) Values(key string) ([]string, bool) { + if md == nil { + return nil, false + } // fast path val, ok := md[key] if !ok { @@ -75,16 +78,42 @@ func (md Metadata) Get(key string) (string, bool) { return val, ok } +// Get returns value from metadata by key +func (md Metadata) Get(key string) (string, bool) { + if md == nil { + return "", false + } + // fast path + val, ok := md[key] + if !ok { + // slow path + val, ok = md[textproto.CanonicalMIMEHeaderKey(key)] + } + if len(val) == 0 { + return "", false + } + return val[0], ok +} + // Set is used to store value in metadata func (md Metadata) Set(kv ...string) { + if md == nil { + return + } 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] + md[textproto.CanonicalMIMEHeaderKey(kv[idx])] = []string{kv[idx+1]} } } +// Add is used to append value in metadata +func (md Metadata) Add(k string, v string) { + kn := textproto.CanonicalMIMEHeaderKey(k) + md[kn] = append(md[kn], v) +} + // Del is used to remove value from metadata func (md Metadata) Del(keys ...string) { for _, key := range keys { @@ -99,7 +128,7 @@ func (md Metadata) Del(keys ...string) { func Copy(md Metadata) Metadata { nmd := New(len(md)) for key, val := range md { - nmd.Set(key, val) + nmd[key] = val } return nmd } @@ -114,16 +143,24 @@ func New(size int) Metadata { // 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] + for key, nval := range mmd { + oval, ok := nmd[key] switch { case ok && !overwrite: continue - case val != "": - nmd.Set(key, val) - case ok && val == "": + case len(nval) == 1 && nval[0] != "": + nmd[key] = nval + case ok && len(nval) > 1: + sort.Strings(nval) + sort.Strings(oval) + for idx, v := range nval { + if oval[idx] != v { + oval[idx] = v + } + } + nmd[key] = oval + case ok && nval[0] == "": nmd.Del(key) } } diff --git a/metadata/metadata_test.go b/metadata/metadata_test.go index eccea7a7..3edb3e16 100644 --- a/metadata/metadata_test.go +++ b/metadata/metadata_test.go @@ -68,10 +68,10 @@ func TestPassing(t *testing.T) { func TestMerge(t *testing.T) { omd := Metadata{ - "key1": "val1", + "key1": []string{"val1"}, } mmd := Metadata{ - "key2": "val2", + "key2": []string{"val2"}, } nmd := Merge(omd, mmd, true) @@ -82,13 +82,14 @@ func TestMerge(t *testing.T) { func TestIterator(t *testing.T) { md := Metadata{ - "1Last": "last", - "2First": "first", - "3Second": "second", + "1Last": []string{"last"}, + "2First": []string{"first"}, + "3Second": []string{"second"}, } iter := md.Iterator() - var k, v string + var k string + var v []string for iter.Next(&k, &v) { // fmt.Printf("k: %s, v: %s\n", k, v) @@ -101,20 +102,20 @@ func TestMedataCanonicalKey(t *testing.T) { v, ok := md.Get("x-request-id") if !ok { t.Fatalf("failed to get x-request-id") - } else if v != "12345" { + } else if len(v) != 1 && v != "12345" { t.Fatalf("invalid metadata value: %s != %s", "12345", v) } v, ok = md.Get("X-Request-Id") if !ok { t.Fatalf("failed to get x-request-id") - } else if v != "12345" { + } else if len(v) != 1 && v != "12345" { t.Fatalf("invalid metadata value: %s != %s", "12345", v) } v, ok = md.Get("X-Request-ID") if !ok { t.Fatalf("failed to get x-request-id") - } else if v != "12345" { + } else if len(v) != 1 && v != "12345" { t.Fatalf("invalid metadata value: %s != %s", "12345", v) } } @@ -135,8 +136,8 @@ func TestMetadataSet(t *testing.T) { func TestMetadataDelete(t *testing.T) { md := Metadata{ - "Foo": "bar", - "Baz": "empty", + "Foo": []string{"bar"}, + "Baz": []string{"empty"}, } md.Del("Baz") @@ -157,14 +158,14 @@ func TestNilContext(t *testing.T) { func TestMetadataCopy(t *testing.T) { md := Metadata{ - "Foo": "bar", - "Bar": "baz", + "Foo": []string{"bar"}, + "Bar": []string{"baz"}, } cp := Copy(md) for k, v := range md { - if cv := cp[k]; cv != v { + if cv := cp[k]; len(cv) != len(v) || (cv[0] != v[0] && cv[1] != v[1]) { t.Fatalf("Got %s:%s for %s:%s", k, cv, k, v) } } @@ -172,7 +173,7 @@ func TestMetadataCopy(t *testing.T) { func TestMetadataContext(t *testing.T) { md := Metadata{ - "Foo": "bar", + "Foo": []string{"bar"}, } ctx := NewContext(context.TODO(), md) @@ -182,7 +183,7 @@ func TestMetadataContext(t *testing.T) { t.Errorf("Unexpected error retrieving metadata, got %t", ok) } - if emd["Foo"] != md["Foo"] { + if len(emd["Foo"]) != len(md["Foo"]) || (emd["Foo"][0] != md["Foo"][0]) { t.Errorf("Expected key: %s val: %s, got key: %s val: %s", "Foo", md["Foo"], "Foo", emd["Foo"]) } diff --git a/register/memory.go b/register/memory.go index 85e8b468..ed970d29 100644 --- a/register/memory.go +++ b/register/memory.go @@ -6,6 +6,7 @@ import ( "time" "go.unistack.org/micro/v3/logger" + md "go.unistack.org/micro/v3/metadata" "go.unistack.org/micro/v3/util/id" ) @@ -23,7 +24,7 @@ type node struct { type record struct { Name string Version string - Metadata map[string]string + Metadata md.Metadata Nodes map[string]*node Endpoints []*Endpoint } @@ -136,9 +137,9 @@ func (m *memory) Register(ctx context.Context, s *Service, opts ...RegisterOptio // domain is set in metadata so it can be passed to watchers if s.Metadata == nil { - s.Metadata = map[string]string{"domain": options.Domain} + s.Metadata = map[string][]string{"domain": {options.Domain}} } else { - s.Metadata["domain"] = options.Domain + s.Metadata["domain"] = []string{options.Domain} } // ensure the service name exists @@ -164,15 +165,10 @@ func (m *memory) Register(ctx context.Context, s *Service, opts ...RegisterOptio continue } - metadata := make(map[string]string, len(n.Metadata)) - - // make copy of metadata - for k, v := range n.Metadata { - metadata[k] = v - } + metadata := md.Copy(n.Metadata) // set the domain - metadata["domain"] = options.Domain + metadata["domain"] = []string{options.Domain} // add the node srvs[s.Name][s.Version].Nodes[n.ID] = &node{ @@ -216,9 +212,9 @@ func (m *memory) Deregister(ctx context.Context, s *Service, opts ...DeregisterO // domain is set in metadata so it can be passed to watchers if s.Metadata == nil { - s.Metadata = map[string]string{"domain": options.Domain} + s.Metadata = map[string][]string{"domain": {options.Domain}} } else { - s.Metadata["domain"] = options.Domain + s.Metadata["domain"] = []string{options.Domain} } // if the domain doesn't exist, there is nothing to deregister @@ -426,8 +422,8 @@ func (m *watcher) Next() (*Result, error) { // extract domain from service metadata var domain string - if r.Service.Metadata != nil && len(r.Service.Metadata["domain"]) > 0 { - domain = r.Service.Metadata["domain"] + if r.Service.Metadata != nil && len(r.Service.Metadata["domain"]) > 0 && len(r.Service.Metadata["domain"][0]) > 0 { + domain = r.Service.Metadata["domain"][0] } else { domain = DefaultDomain } @@ -452,10 +448,7 @@ func (m *watcher) Stop() { } 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 - } + metadata := md.Copy(s.Metadata) nodes := make(map[string]*node, len(s.Nodes)) for _, n := range s.Nodes { @@ -481,41 +474,28 @@ func serviceToRecord(s *Service, ttl time.Duration) *record { } func recordToService(r *record, domain string) *Service { - metadata := make(map[string]string, len(r.Metadata)) - for k, v := range r.Metadata { - metadata[k] = v - } + metadata := md.Copy(r.Metadata) // set the domain in metadata so it can be determined when a wildcard query is performed - metadata["domain"] = domain + metadata["domain"] = []string{domain} endpoints := make([]*Endpoint, len(r.Endpoints)) for i, e := range r.Endpoints { - md := make(map[string]string, len(e.Metadata)) - for k, v := range e.Metadata { - md[k] = v - } - endpoints[i] = &Endpoint{ Name: e.Name, Request: e.Request, Response: e.Response, - Metadata: md, + Metadata: md.Copy(e.Metadata), } } nodes := make([]*Node, len(r.Nodes)) i := 0 for _, n := range r.Nodes { - md := make(map[string]string, len(n.Metadata)) - for k, v := range n.Metadata { - md[k] = v - } - nodes[i] = &Node{ ID: n.ID, Address: n.Address, - Metadata: md, + Metadata: md.Copy(n.Metadata), } i++ }