Compare commits

..

5 Commits

Author SHA1 Message Date
75fd1e43b9 Merge pull request #185 from unistack-org/server
server: add server.SetHandlerOption helper
2023-02-13 23:33:57 +03:00
395a3eed3d server: add server.SetHandlerOption helper
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2023-02-13 23:31:38 +03:00
3ba8cb7f9e Merge pull request #184 from unistack-org/duration
util/time: add Marshal/Unmarshal to own Duration
2023-02-13 14:05:16 +03:00
b07806b9a1 tmp
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2023-02-13 14:03:02 +03:00
0f583218d4 tmp
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2023-02-13 14:02:08 +03:00
8 changed files with 137 additions and 84 deletions

View File

@@ -124,7 +124,7 @@ func AppendOutgoingContext(ctx context.Context, kv ...string) context.Context {
return NewOutgoingContext(ctx, md) return NewOutgoingContext(ctx, md)
} }
for k, v := range md { for k, v := range md {
omd[k] = v omd.Set(k, v)
} }
return NewOutgoingContext(ctx, omd) return NewOutgoingContext(ctx, omd)
} }
@@ -140,7 +140,7 @@ func AppendIncomingContext(ctx context.Context, kv ...string) context.Context {
return NewIncomingContext(ctx, md) return NewIncomingContext(ctx, md)
} }
for k, v := range md { for k, v := range md {
omd[k] = v omd.Set(k, v)
} }
return NewIncomingContext(ctx, omd) return NewIncomingContext(ctx, omd)
} }

View File

@@ -24,7 +24,7 @@ var (
// Metadata is our way of representing request headers internally. // Metadata is our way of representing request headers internally.
// They're used at the RPC level and translate back and forth // They're used at the RPC level and translate back and forth
// from Transport headers. // from Transport headers.
type Metadata map[string][]string type Metadata map[string]string
type rawMetadata struct { type rawMetadata struct {
md Metadata md Metadata
@@ -42,7 +42,7 @@ type Iterator struct {
} }
// Next advance iterator to next element // Next advance iterator to next element
func (iter *Iterator) Next(k *string, v *[]string) bool { func (iter *Iterator) Next(k, v *string) bool {
if iter.cur+1 > iter.cnt { if iter.cur+1 > iter.cnt {
return false return false
} }
@@ -64,11 +64,8 @@ func (md Metadata) Iterator() *Iterator {
return iter return iter
} }
// Values returns values from metadata by key // Get returns value from metadata by key
func (md Metadata) Values(key string) ([]string, bool) { func (md Metadata) Get(key string) (string, bool) {
if md == nil {
return nil, false
}
// fast path // fast path
val, ok := md[key] val, ok := md[key]
if !ok { if !ok {
@@ -78,42 +75,16 @@ func (md Metadata) Values(key string) ([]string, bool) {
return val, ok 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 // Set is used to store value in metadata
func (md Metadata) Set(kv ...string) { func (md Metadata) Set(kv ...string) {
if md == nil {
return
}
if len(kv)%2 == 1 { if len(kv)%2 == 1 {
kv = kv[:len(kv)-1] kv = kv[:len(kv)-1]
} }
for idx := 0; idx < len(kv); idx += 2 { for idx := 0; idx < len(kv); idx += 2 {
md[textproto.CanonicalMIMEHeaderKey(kv[idx])] = []string{kv[idx+1]} md[textproto.CanonicalMIMEHeaderKey(kv[idx])] = 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 // Del is used to remove value from metadata
func (md Metadata) Del(keys ...string) { func (md Metadata) Del(keys ...string) {
for _, key := range keys { for _, key := range keys {
@@ -128,7 +99,7 @@ func (md Metadata) Del(keys ...string) {
func Copy(md Metadata) Metadata { func Copy(md Metadata) Metadata {
nmd := New(len(md)) nmd := New(len(md))
for key, val := range md { for key, val := range md {
nmd[key] = val nmd.Set(key, val)
} }
return nmd return nmd
} }
@@ -143,24 +114,16 @@ func New(size int) Metadata {
// Merge merges metadata to existing metadata, overwriting if specified // Merge merges metadata to existing metadata, overwriting if specified
func Merge(omd Metadata, mmd Metadata, overwrite bool) Metadata { func Merge(omd Metadata, mmd Metadata, overwrite bool) Metadata {
var ok bool
nmd := Copy(omd) nmd := Copy(omd)
for key, nval := range mmd { for key, val := range mmd {
oval, ok := nmd[key] _, ok = nmd[key]
switch { switch {
case ok && !overwrite: case ok && !overwrite:
continue continue
case len(nval) == 1 && nval[0] != "": case val != "":
nmd[key] = nval nmd.Set(key, val)
case ok && len(nval) > 1: case ok && val == "":
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) nmd.Del(key)
} }
} }

View File

@@ -68,10 +68,10 @@ func TestPassing(t *testing.T) {
func TestMerge(t *testing.T) { func TestMerge(t *testing.T) {
omd := Metadata{ omd := Metadata{
"key1": []string{"val1"}, "key1": "val1",
} }
mmd := Metadata{ mmd := Metadata{
"key2": []string{"val2"}, "key2": "val2",
} }
nmd := Merge(omd, mmd, true) nmd := Merge(omd, mmd, true)
@@ -82,14 +82,13 @@ func TestMerge(t *testing.T) {
func TestIterator(t *testing.T) { func TestIterator(t *testing.T) {
md := Metadata{ md := Metadata{
"1Last": []string{"last"}, "1Last": "last",
"2First": []string{"first"}, "2First": "first",
"3Second": []string{"second"}, "3Second": "second",
} }
iter := md.Iterator() iter := md.Iterator()
var k string var k, v string
var v []string
for iter.Next(&k, &v) { for iter.Next(&k, &v) {
// fmt.Printf("k: %s, v: %s\n", k, v) // fmt.Printf("k: %s, v: %s\n", k, v)
@@ -102,20 +101,20 @@ func TestMedataCanonicalKey(t *testing.T) {
v, ok := md.Get("x-request-id") v, ok := md.Get("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 len(v) != 1 && 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.Get("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 len(v) != 1 && 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.Get("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 len(v) != 1 && v != "12345" { } else if v != "12345" {
t.Fatalf("invalid metadata value: %s != %s", "12345", v) t.Fatalf("invalid metadata value: %s != %s", "12345", v)
} }
} }
@@ -136,8 +135,8 @@ func TestMetadataSet(t *testing.T) {
func TestMetadataDelete(t *testing.T) { func TestMetadataDelete(t *testing.T) {
md := Metadata{ md := Metadata{
"Foo": []string{"bar"}, "Foo": "bar",
"Baz": []string{"empty"}, "Baz": "empty",
} }
md.Del("Baz") md.Del("Baz")
@@ -158,14 +157,14 @@ func TestNilContext(t *testing.T) {
func TestMetadataCopy(t *testing.T) { func TestMetadataCopy(t *testing.T) {
md := Metadata{ md := Metadata{
"Foo": []string{"bar"}, "Foo": "bar",
"Bar": []string{"baz"}, "Bar": "baz",
} }
cp := Copy(md) cp := Copy(md)
for k, v := range md { for k, v := range md {
if cv := cp[k]; len(cv) != len(v) || (cv[0] != v[0] && cv[1] != v[1]) { if cv := cp[k]; cv != v {
t.Fatalf("Got %s:%s for %s:%s", k, cv, k, v) t.Fatalf("Got %s:%s for %s:%s", k, cv, k, v)
} }
} }
@@ -173,7 +172,7 @@ func TestMetadataCopy(t *testing.T) {
func TestMetadataContext(t *testing.T) { func TestMetadataContext(t *testing.T) {
md := Metadata{ md := Metadata{
"Foo": []string{"bar"}, "Foo": "bar",
} }
ctx := NewContext(context.TODO(), md) ctx := NewContext(context.TODO(), md)
@@ -183,7 +182,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 len(emd["Foo"]) != len(md["Foo"]) || (emd["Foo"][0] != md["Foo"][0]) { if emd["Foo"] != md["Foo"] {
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"])
} }

View File

@@ -6,7 +6,6 @@ import (
"time" "time"
"go.unistack.org/micro/v3/logger" "go.unistack.org/micro/v3/logger"
md "go.unistack.org/micro/v3/metadata"
"go.unistack.org/micro/v3/util/id" "go.unistack.org/micro/v3/util/id"
) )
@@ -24,7 +23,7 @@ type node struct {
type record struct { type record struct {
Name string Name string
Version string Version string
Metadata md.Metadata Metadata map[string]string
Nodes map[string]*node Nodes map[string]*node
Endpoints []*Endpoint Endpoints []*Endpoint
} }
@@ -137,9 +136,9 @@ func (m *memory) Register(ctx context.Context, s *Service, opts ...RegisterOptio
// domain is set in metadata so it can be passed to watchers // domain is set in metadata so it can be passed to watchers
if s.Metadata == nil { if s.Metadata == nil {
s.Metadata = map[string][]string{"domain": {options.Domain}} s.Metadata = map[string]string{"domain": options.Domain}
} else { } else {
s.Metadata["domain"] = []string{options.Domain} s.Metadata["domain"] = options.Domain
} }
// ensure the service name exists // ensure the service name exists
@@ -165,10 +164,15 @@ func (m *memory) Register(ctx context.Context, s *Service, opts ...RegisterOptio
continue continue
} }
metadata := md.Copy(n.Metadata) metadata := make(map[string]string, len(n.Metadata))
// make copy of metadata
for k, v := range n.Metadata {
metadata[k] = v
}
// set the domain // set the domain
metadata["domain"] = []string{options.Domain} metadata["domain"] = options.Domain
// add the node // add the node
srvs[s.Name][s.Version].Nodes[n.ID] = &node{ srvs[s.Name][s.Version].Nodes[n.ID] = &node{
@@ -212,9 +216,9 @@ func (m *memory) Deregister(ctx context.Context, s *Service, opts ...DeregisterO
// domain is set in metadata so it can be passed to watchers // domain is set in metadata so it can be passed to watchers
if s.Metadata == nil { if s.Metadata == nil {
s.Metadata = map[string][]string{"domain": {options.Domain}} s.Metadata = map[string]string{"domain": options.Domain}
} else { } else {
s.Metadata["domain"] = []string{options.Domain} s.Metadata["domain"] = options.Domain
} }
// if the domain doesn't exist, there is nothing to deregister // if the domain doesn't exist, there is nothing to deregister
@@ -422,8 +426,8 @@ func (m *watcher) Next() (*Result, error) {
// extract domain from service metadata // extract domain from service metadata
var domain string var domain string
if r.Service.Metadata != nil && len(r.Service.Metadata["domain"]) > 0 && len(r.Service.Metadata["domain"][0]) > 0 { if r.Service.Metadata != nil && len(r.Service.Metadata["domain"]) > 0 {
domain = r.Service.Metadata["domain"][0] domain = r.Service.Metadata["domain"]
} else { } else {
domain = DefaultDomain domain = DefaultDomain
} }
@@ -448,7 +452,10 @@ func (m *watcher) Stop() {
} }
func serviceToRecord(s *Service, ttl time.Duration) *record { func serviceToRecord(s *Service, ttl time.Duration) *record {
metadata := md.Copy(s.Metadata) 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)) nodes := make(map[string]*node, len(s.Nodes))
for _, n := range s.Nodes { for _, n := range s.Nodes {
@@ -474,28 +481,41 @@ func serviceToRecord(s *Service, ttl time.Duration) *record {
} }
func recordToService(r *record, domain string) *Service { func recordToService(r *record, domain string) *Service {
metadata := md.Copy(r.Metadata) 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 // set the domain in metadata so it can be determined when a wildcard query is performed
metadata["domain"] = []string{domain} metadata["domain"] = domain
endpoints := make([]*Endpoint, len(r.Endpoints)) endpoints := make([]*Endpoint, len(r.Endpoints))
for i, e := range 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{ endpoints[i] = &Endpoint{
Name: e.Name, Name: e.Name,
Request: e.Request, Request: e.Request,
Response: e.Response, Response: e.Response,
Metadata: md.Copy(e.Metadata), Metadata: md,
} }
} }
nodes := make([]*Node, len(r.Nodes)) nodes := make([]*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))
for k, v := range n.Metadata {
md[k] = v
}
nodes[i] = &Node{ nodes[i] = &Node{
ID: n.ID, ID: n.ID,
Address: n.Address, Address: n.Address,
Metadata: md.Copy(n.Metadata), Metadata: md,
} }
i++ i++
} }

View File

@@ -42,3 +42,13 @@ func SetSubscriberOption(k, v interface{}) SubscriberOption {
o.Context = context.WithValue(o.Context, k, v) o.Context = context.WithValue(o.Context, k, v)
} }
} }
// SetHandlerOption returns a function to setup a context with given value
func SetHandlerOption(k, v interface{}) HandlerOption {
return func(o *HandlerOptions) {
if o.Context == nil {
o.Context = context.Background()
}
o.Context = context.WithValue(o.Context, k, v)
}
}

View File

@@ -322,7 +322,7 @@ type HandlerOption func(*HandlerOptions)
type HandlerOptions struct { type HandlerOptions struct {
// Context holds external options // Context holds external options
Context context.Context Context context.Context
// Metadata for hondler // Metadata for handler
Metadata map[string]metadata.Metadata Metadata map[string]metadata.Metadata
} }

View File

@@ -1,6 +1,7 @@
package time package time
import ( import (
"encoding/json"
"fmt" "fmt"
"time" "time"
) )
@@ -46,3 +47,36 @@ func ParseDuration(s string) (time.Duration, error) {
return td, err return td, err
} }
func (d Duration) MarshalJSON() ([]byte, error) {
return json.Marshal(time.Duration(d).String())
}
func (d *Duration) UnmarshalJSON(b []byte) error {
var v interface{}
if err := json.Unmarshal(b, &v); err != nil {
return err
}
switch value := v.(type) {
case float64:
*d = Duration(time.Duration(value))
return nil
case string:
dv, err := time.ParseDuration(value)
if err != nil {
return err
}
*d = Duration(dv)
return nil
default:
return fmt.Errorf("invalid duration")
}
}
/*
func (d Duration) MarshalYAML() (interface{}, error) {
return nil, nil
}
func (d Duration) UnmarshalYAML(fn func(interface{}) error) error
*/

View File

@@ -1,10 +1,37 @@
package time package time
import ( import (
"bytes"
"encoding/json"
"testing" "testing"
"time" "time"
) )
func TestMarshalJSON(t *testing.T) {
d := Duration(10000000)
buf, err := json.Marshal(d)
if err != nil {
t.Fatal(err)
}
if !bytes.Equal(buf, []byte(`"10ms"`)) {
t.Fatalf("invalid duration: %s != %s", buf, `"10ms"`)
}
}
func TestUnmarshalJSON(t *testing.T) {
type str struct {
TTL Duration `json:"ttl"`
}
v := &str{}
err := json.Unmarshal([]byte(`{"ttl":"10ms"}`), v)
if err != nil {
t.Fatal(err)
} else if v.TTL != 10000000 {
t.Fatalf("invalid duration %v != 10000000", v.TTL)
}
}
func TestParseDuration(t *testing.T) { func TestParseDuration(t *testing.T) {
var td time.Duration var td time.Duration
var err error var err error