Merge master

This commit is contained in:
Ben Toogood 2020-05-26 15:52:21 +01:00
commit 5712cc9c62
17 changed files with 577 additions and 148 deletions

View File

@ -42,7 +42,7 @@ type Auth interface {
// Revoke access to a resource
Revoke(rule *Rule) error
// Rules returns all the rules used to verify requests
Rules() ([]*Rule, error)
Rules(...RulesOption) ([]*Rule, error)
// String returns the name of the implementation
String() string
}

View File

@ -68,7 +68,7 @@ func (n *noop) Revoke(rule *Rule) error {
}
// Rules used to verify requests
func (n *noop) Rules() ([]*Rule, error) {
func (n *noop) Rules(opts ...RulesOption) ([]*Rule, error) {
return []*Rule{}, nil
}

View File

@ -105,7 +105,7 @@ func (j *jwt) Verify(acc *auth.Account, res *auth.Resource, opts ...auth.VerifyO
return rules.Verify(j.rules, acc, res)
}
func (j *jwt) Rules() ([]*auth.Rule, error) {
func (j *jwt) Rules(opts ...auth.RulesOption) ([]*auth.Rule, error) {
j.Lock()
defer j.Unlock()
return j.rules, nil

View File

@ -1,6 +1,7 @@
package auth
import (
"context"
"time"
"github.com/micro/go-micro/v2/auth/provider"
@ -226,13 +227,25 @@ func NewTokenOptions(opts ...TokenOption) TokenOptions {
}
type VerifyOptions struct {
Namespace string
Context context.Context
}
type VerifyOption func(o *VerifyOptions)
func VerifyNamespace(ns string) VerifyOption {
func VerifyContext(ctx context.Context) VerifyOption {
return func(o *VerifyOptions) {
o.Namespace = ns
o.Context = ctx
}
}
type RulesOptions struct {
Context context.Context
}
type RulesOption func(o *RulesOptions)
func RulesContext(ctx context.Context) RulesOption {
return func(o *RulesOptions) {
o.Context = ctx
}
}

View File

@ -3,7 +3,6 @@ package service
import (
"context"
"strings"
"sync"
"time"
"github.com/micro/go-micro/v2/auth"
@ -12,19 +11,14 @@ import (
"github.com/micro/go-micro/v2/auth/token"
"github.com/micro/go-micro/v2/auth/token/jwt"
"github.com/micro/go-micro/v2/client"
log "github.com/micro/go-micro/v2/logger"
"github.com/micro/go-micro/v2/metadata"
"github.com/micro/go-micro/v2/util/jitter"
)
// svc is the service implementation of the Auth interface
type svc struct {
options auth.Options
auth pb.AuthService
rule pb.RulesService
rules pb.RulesService
jwt token.Provider
rules map[string][]*auth.Rule
sync.Mutex
}
func (s *svc) String() string {
@ -41,7 +35,7 @@ func (s *svc) Init(opts ...auth.Option) {
}
s.auth = pb.NewAuthService("go.micro.auth", s.options.Client)
s.rule = pb.NewRulesService("go.micro.auth", s.options.Client)
s.rules = pb.NewRulesService("go.micro.auth", s.options.Client)
// if we have a JWT public key passed as an option,
// we can decode tokens with the type "JWT" locally
@ -52,8 +46,6 @@ func (s *svc) Init(opts ...auth.Option) {
}
func (s *svc) Options() auth.Options {
s.Lock()
defer s.Unlock()
return s.options
}
@ -85,7 +77,7 @@ func (s *svc) Grant(rule *auth.Rule) error {
access = pb.Access_DENIED
}
_, err := s.rule.Create(context.TODO(), &pb.CreateRequest{
_, err := s.rules.Create(context.TODO(), &pb.CreateRequest{
Rule: &pb.Rule{
Id: rule.ID,
Scope: rule.Scope,
@ -99,25 +91,38 @@ func (s *svc) Grant(rule *auth.Rule) error {
},
})
if err == nil {
go s.loadRules(s.options.Namespace)
}
return err
}
// Revoke access to a resource
func (s *svc) Revoke(rule *auth.Rule) error {
_, err := s.rule.Delete(context.TODO(), &pb.DeleteRequest{
_, err := s.rules.Delete(context.TODO(), &pb.DeleteRequest{
Id: rule.ID,
})
go s.loadRules(s.options.Namespace)
return err
}
func (s *svc) Rules() ([]*auth.Rule, error) {
return s.rules[s.options.Namespace], nil
func (s *svc) Rules(opts ...auth.RulesOption) ([]*auth.Rule, error) {
var options auth.RulesOptions
for _, o := range opts {
o(&options)
}
if options.Context == nil {
options.Context = context.TODO()
}
rsp, err := s.rules.List(options.Context, &pb.ListRequest{})
if err != nil {
return nil, err
}
rules := make([]*auth.Rule, len(rsp.Rules))
for i, r := range rsp.Rules {
rules[i] = serializeRule(r)
}
return rules, nil
}
// Verify an account has access to a resource
@ -126,15 +131,13 @@ func (s *svc) Verify(acc *auth.Account, res *auth.Resource, opts ...auth.VerifyO
for _, o := range opts {
o(&options)
}
if len(options.Namespace) == 0 {
options.Namespace = s.options.Namespace
rs, err := s.Rules(auth.RulesContext(options.Context))
if err != nil {
return err
}
// load the rules if none are loaded
s.loadRulesIfEmpty(options.Namespace)
// verify the request using the rules
return rules.Verify(s.rules[options.Namespace], acc, res)
return rules.Verify(rs, acc, res)
}
// Inspect a token
@ -170,53 +173,6 @@ func (s *svc) Token(opts ...auth.TokenOption) (*auth.Token, error) {
return serializeToken(rsp.Token), nil
}
// loadRules retrieves the rules from the auth service. Since this implementation is used by micro
// clients, which support muti-tenancy we may have to persist rules in multiple namespaces.
func (s *svc) loadRules(namespace string) {
ctx := metadata.Set(context.TODO(), "Micro-Namespace", namespace)
rsp, err := s.rule.List(ctx, &pb.ListRequest{})
if err != nil {
log.Errorf("Error listing rules: %v", err)
return
}
rules := make([]*auth.Rule, 0, len(rsp.Rules))
for _, r := range rsp.Rules {
var access auth.Access
if r.Access == pb.Access_GRANTED {
access = auth.AccessGranted
} else {
access = auth.AccessDenied
}
rules = append(rules, &auth.Rule{
ID: r.Id,
Scope: r.Scope,
Access: access,
Priority: r.Priority,
Resource: &auth.Resource{
Type: r.Resource.Type,
Name: r.Resource.Name,
Endpoint: r.Resource.Endpoint,
},
})
}
s.Lock()
s.rules[namespace] = rules
s.Unlock()
}
func (s *svc) loadRulesIfEmpty(namespace string) {
s.Lock()
rules := s.rules
s.Unlock()
if _, ok := rules[namespace]; !ok {
s.loadRules(namespace)
}
}
func serializeToken(t *pb.Token) *auth.Token {
return &auth.Token{
AccessToken: t.AccessToken,
@ -236,6 +192,27 @@ func serializeAccount(a *pb.Account) *auth.Account {
}
}
func serializeRule(r *pb.Rule) *auth.Rule {
var access auth.Access
if r.Access == pb.Access_GRANTED {
access = auth.AccessGranted
} else {
access = auth.AccessDenied
}
return &auth.Rule{
ID: r.Id,
Scope: r.Scope,
Access: access,
Priority: r.Priority,
Resource: &auth.Resource{
Type: r.Resource.Type,
Name: r.Resource.Name,
Endpoint: r.Resource.Endpoint,
},
}
}
// NewAuth returns a new instance of the Auth service
func NewAuth(opts ...auth.Option) auth.Auth {
options := auth.NewOptions(opts...)
@ -243,26 +220,9 @@ func NewAuth(opts ...auth.Option) auth.Auth {
options.Client = client.DefaultClient
}
service := &svc{
return &svc{
auth: pb.NewAuthService("go.micro.auth", options.Client),
rule: pb.NewRulesService("go.micro.auth", options.Client),
rules: make(map[string][]*auth.Rule),
rules: pb.NewRulesService("go.micro.auth", options.Client),
options: options,
}
// load rules periodically from the auth service
go func() {
ruleTimer := time.NewTicker(time.Second * 30)
for {
<-ruleTimer.C
time.Sleep(jitter.Do(time.Second * 5))
for ns := range service.rules {
service.loadRules(ns)
}
}
}()
return service
}

66
client/cache.go Normal file
View File

@ -0,0 +1,66 @@
package client
import (
"context"
"encoding/json"
"fmt"
"hash/fnv"
"time"
"github.com/micro/go-micro/v2/metadata"
cache "github.com/patrickmn/go-cache"
)
// NewCache returns an initialised cache.
func NewCache() *Cache {
return &Cache{
cache: cache.New(cache.NoExpiration, 30*time.Second),
}
}
// Cache for responses
type Cache struct {
cache *cache.Cache
}
// Get a response from the cache
func (c *Cache) Get(ctx context.Context, req *Request) (interface{}, bool) {
return c.cache.Get(key(ctx, req))
}
// Set a response in the cache
func (c *Cache) Set(ctx context.Context, req *Request, rsp interface{}, expiry time.Duration) {
c.cache.Set(key(ctx, req), rsp, expiry)
}
// List the key value pairs in the cache
func (c *Cache) List() map[string]string {
items := c.cache.Items()
rsp := make(map[string]string, len(items))
for k, v := range items {
bytes, _ := json.Marshal(v.Object)
rsp[k] = string(bytes)
}
return rsp
}
// key returns a hash for the context and request
func key(ctx context.Context, req *Request) string {
md, _ := metadata.FromContext(ctx)
bytes, _ := json.Marshal(map[string]interface{}{
"metadata": md,
"request": map[string]interface{}{
"service": (*req).Service(),
"endpoint": (*req).Endpoint(),
"method": (*req).Method(),
"body": (*req).Body(),
},
})
h := fnv.New64()
h.Write(bytes)
return fmt.Sprintf("%x", h.Sum(nil))
}

76
client/cache_test.go Normal file
View File

@ -0,0 +1,76 @@
package client
import (
"context"
"testing"
"time"
"github.com/micro/go-micro/v2/metadata"
)
func TestCache(t *testing.T) {
ctx := context.TODO()
req := NewRequest("go.micro.service.foo", "Foo.Bar", nil)
t.Run("CacheMiss", func(t *testing.T) {
if _, ok := NewCache().Get(ctx, &req); ok {
t.Errorf("Expected to get no result from Get")
}
})
t.Run("CacheHit", func(t *testing.T) {
c := NewCache()
rsp := "theresponse"
c.Set(ctx, &req, rsp, time.Minute)
if res, ok := c.Get(ctx, &req); !ok {
t.Errorf("Expected a result, got nothing")
} else if res != rsp {
t.Errorf("Expected '%v' result, got '%v'", rsp, res)
}
})
}
func TestCacheKey(t *testing.T) {
ctx := context.TODO()
req1 := NewRequest("go.micro.service.foo", "Foo.Bar", nil)
req2 := NewRequest("go.micro.service.foo", "Foo.Baz", nil)
req3 := NewRequest("go.micro.service.foo", "Foo.Baz", "customquery")
t.Run("IdenticalRequests", func(t *testing.T) {
key1 := key(ctx, &req1)
key2 := key(ctx, &req1)
if key1 != key2 {
t.Errorf("Expected the keys to match for identical requests and context")
}
})
t.Run("DifferentRequestEndpoints", func(t *testing.T) {
key1 := key(ctx, &req1)
key2 := key(ctx, &req2)
if key1 == key2 {
t.Errorf("Expected the keys to differ for different request endpoints")
}
})
t.Run("DifferentRequestBody", func(t *testing.T) {
key1 := key(ctx, &req2)
key2 := key(ctx, &req3)
if key1 == key2 {
t.Errorf("Expected the keys to differ for different request bodies")
}
})
t.Run("DifferentMetadata", func(t *testing.T) {
mdCtx := metadata.Set(context.TODO(), "foo", "bar")
key1 := key(mdCtx, &req1)
key2 := key(ctx, &req1)
if key1 == key2 {
t.Errorf("Expected the keys to differ for different metadata")
}
})
}

View File

@ -29,6 +29,9 @@ type Options struct {
PoolSize int
PoolTTL time.Duration
// Response cache
Cache *Cache
// Middleware for client
Wrappers []Wrapper
@ -59,6 +62,8 @@ type CallOptions struct {
StreamTimeout time.Duration
// Use the services own auth token
ServiceToken bool
// Duration to cache the response for
CacheExpiry time.Duration
// Middleware for low level call func
CallWrappers []CallWrapper
@ -91,6 +96,7 @@ type RequestOptions struct {
func NewOptions(options ...Option) Options {
opts := Options{
Cache: NewCache(),
Context: context.Background(),
ContentType: DefaultContentType,
Codecs: make(map[string]codec.NewCodec),
@ -324,6 +330,14 @@ func WithServiceToken() CallOption {
}
}
// WithCache is a CallOption which sets the duration the response
// shoull be cached for
func WithCache(c time.Duration) CallOption {
return func(o *CallOptions) {
o.CacheExpiry = c
}
}
func WithMessageContentType(ct string) MessageOption {
return func(o *MessageOptions) {
o.ContentType = ct

View File

@ -472,9 +472,13 @@ func (c *cmd) Before(ctx *cli.Context) error {
var serverOpts []server.Option
var clientOpts []client.Option
// setup a client to use when calling the runtime
// setup a client to use when calling the runtime. It is important the auth client is wrapped
// after the cache client since the wrappers are applied in reverse order and the cache will use
// some of the headers set by the auth client.
authFn := func() auth.Auth { return *c.opts.Auth }
microClient := wrapper.AuthClient(authFn, grpc.NewClient())
cacheFn := func() *client.Cache { return (*c.opts.Client).Options().Cache }
microClient := wrapper.CacheClient(cacheFn, grpc.NewClient())
microClient = wrapper.AuthClient(authFn, microClient)
// Set the store
if name := ctx.String("store"); len(name) > 0 {

View File

@ -5,6 +5,7 @@ import (
"context"
"time"
"github.com/micro/go-micro/v2/client"
"github.com/micro/go-micro/v2/debug/log"
proto "github.com/micro/go-micro/v2/debug/service/proto"
"github.com/micro/go-micro/v2/debug/stats"
@ -13,11 +14,12 @@ import (
)
// NewHandler returns an instance of the Debug Handler
func NewHandler() *Debug {
func NewHandler(c client.Client) *Debug {
return &Debug{
log: log.DefaultLog,
stats: stats.DefaultStats,
trace: trace.DefaultTracer,
cache: c.Options().Cache,
}
}
@ -30,6 +32,8 @@ type Debug struct {
stats stats.Stats
// the tracer
trace trace.Tracer
// the cache
cache *client.Cache
}
func (d *Debug) Health(ctx context.Context, req *proto.HealthRequest, rsp *proto.HealthResponse) error {
@ -164,3 +168,9 @@ func (d *Debug) Log(ctx context.Context, stream server.Stream) error {
return nil
}
// Cache returns all the key value pairs in the client cache
func (d *Debug) Cache(ctx context.Context, req *proto.CacheRequest, rsp *proto.CacheResponse) error {
rsp.Values = d.cache.List()
return nil
}

View File

@ -582,6 +582,76 @@ func (m *Span) GetType() SpanType {
return SpanType_INBOUND
}
type CacheRequest struct {
XXX_NoUnkeyedLiteral struct{} `json:"-"`
XXX_unrecognized []byte `json:"-"`
XXX_sizecache int32 `json:"-"`
}
func (m *CacheRequest) Reset() { *m = CacheRequest{} }
func (m *CacheRequest) String() string { return proto.CompactTextString(m) }
func (*CacheRequest) ProtoMessage() {}
func (*CacheRequest) Descriptor() ([]byte, []int) {
return fileDescriptor_df91f41a5db378e6, []int{9}
}
func (m *CacheRequest) XXX_Unmarshal(b []byte) error {
return xxx_messageInfo_CacheRequest.Unmarshal(m, b)
}
func (m *CacheRequest) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
return xxx_messageInfo_CacheRequest.Marshal(b, m, deterministic)
}
func (m *CacheRequest) XXX_Merge(src proto.Message) {
xxx_messageInfo_CacheRequest.Merge(m, src)
}
func (m *CacheRequest) XXX_Size() int {
return xxx_messageInfo_CacheRequest.Size(m)
}
func (m *CacheRequest) XXX_DiscardUnknown() {
xxx_messageInfo_CacheRequest.DiscardUnknown(m)
}
var xxx_messageInfo_CacheRequest proto.InternalMessageInfo
type CacheResponse struct {
Values map[string]string `protobuf:"bytes,1,rep,name=values,proto3" json:"values,omitempty" protobuf_key:"bytes,1,opt,name=key,proto3" protobuf_val:"bytes,2,opt,name=value,proto3"`
XXX_NoUnkeyedLiteral struct{} `json:"-"`
XXX_unrecognized []byte `json:"-"`
XXX_sizecache int32 `json:"-"`
}
func (m *CacheResponse) Reset() { *m = CacheResponse{} }
func (m *CacheResponse) String() string { return proto.CompactTextString(m) }
func (*CacheResponse) ProtoMessage() {}
func (*CacheResponse) Descriptor() ([]byte, []int) {
return fileDescriptor_df91f41a5db378e6, []int{10}
}
func (m *CacheResponse) XXX_Unmarshal(b []byte) error {
return xxx_messageInfo_CacheResponse.Unmarshal(m, b)
}
func (m *CacheResponse) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
return xxx_messageInfo_CacheResponse.Marshal(b, m, deterministic)
}
func (m *CacheResponse) XXX_Merge(src proto.Message) {
xxx_messageInfo_CacheResponse.Merge(m, src)
}
func (m *CacheResponse) XXX_Size() int {
return xxx_messageInfo_CacheResponse.Size(m)
}
func (m *CacheResponse) XXX_DiscardUnknown() {
xxx_messageInfo_CacheResponse.DiscardUnknown(m)
}
var xxx_messageInfo_CacheResponse proto.InternalMessageInfo
func (m *CacheResponse) GetValues() map[string]string {
if m != nil {
return m.Values
}
return nil
}
func init() {
proto.RegisterEnum("SpanType", SpanType_name, SpanType_value)
proto.RegisterType((*HealthRequest)(nil), "HealthRequest")
@ -595,50 +665,56 @@ func init() {
proto.RegisterType((*TraceResponse)(nil), "TraceResponse")
proto.RegisterType((*Span)(nil), "Span")
proto.RegisterMapType((map[string]string)(nil), "Span.MetadataEntry")
proto.RegisterType((*CacheRequest)(nil), "CacheRequest")
proto.RegisterType((*CacheResponse)(nil), "CacheResponse")
proto.RegisterMapType((map[string]string)(nil), "CacheResponse.ValuesEntry")
}
func init() { proto.RegisterFile("debug/service/proto/debug.proto", fileDescriptor_df91f41a5db378e6) }
var fileDescriptor_df91f41a5db378e6 = []byte{
// 594 bytes of a gzipped FileDescriptorProto
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xa4, 0x54, 0xdb, 0x6e, 0xd3, 0x40,
0x10, 0x8d, 0xed, 0x38, 0xb1, 0xa7, 0x8d, 0xa9, 0x96, 0x8b, 0x2c, 0x73, 0x69, 0x65, 0x09, 0x29,
0x5c, 0xe4, 0x40, 0x79, 0x41, 0xf0, 0x86, 0x8a, 0x04, 0x52, 0x69, 0xa5, 0x6d, 0xfb, 0x01, 0x5b,
0x7b, 0xe4, 0x1a, 0xea, 0x0b, 0xbb, 0xeb, 0x4a, 0xf9, 0x16, 0xbe, 0x80, 0x37, 0x7e, 0x86, 0xff,
0x41, 0x7b, 0x71, 0x1b, 0x0b, 0xa1, 0x3e, 0xf0, 0xb6, 0xe7, 0xec, 0xec, 0xc9, 0xcc, 0xc9, 0xf1,
0xc0, 0x6e, 0x81, 0xe7, 0x7d, 0xb9, 0x12, 0xc8, 0xaf, 0xaa, 0x1c, 0x57, 0x1d, 0x6f, 0x65, 0xbb,
0xd2, 0x5c, 0xa6, 0xcf, 0xe9, 0x33, 0x58, 0x7c, 0x42, 0x76, 0x29, 0x2f, 0x28, 0x7e, 0xef, 0x51,
0x48, 0x12, 0xc3, 0xdc, 0x56, 0xc7, 0xce, 0x9e, 0xb3, 0x0c, 0xe9, 0x00, 0xd3, 0x25, 0x44, 0x43,
0xa9, 0xe8, 0xda, 0x46, 0x20, 0x79, 0x00, 0x33, 0x21, 0x99, 0xec, 0x85, 0x2d, 0xb5, 0x28, 0x5d,
0xc2, 0xf6, 0x89, 0x64, 0x52, 0xdc, 0xae, 0xf9, 0xdb, 0x81, 0x85, 0x2d, 0xb5, 0x9a, 0x8f, 0x20,
0x94, 0x55, 0x8d, 0x42, 0xb2, 0xba, 0xd3, 0xd5, 0x53, 0x7a, 0x43, 0x68, 0x25, 0xc9, 0xb8, 0xc4,
0x22, 0x76, 0xf5, 0xdd, 0x00, 0x55, 0x2f, 0x7d, 0xa7, 0x0a, 0x63, 0x4f, 0x5f, 0x58, 0xa4, 0xf8,
0x1a, 0xeb, 0x96, 0xaf, 0xe3, 0xa9, 0xe1, 0x0d, 0x52, 0x4a, 0xf2, 0x82, 0x23, 0x2b, 0x44, 0xec,
0x1b, 0x25, 0x0b, 0x49, 0x04, 0x6e, 0x99, 0xc7, 0x33, 0x4d, 0xba, 0x65, 0x4e, 0x12, 0x08, 0xb8,
0x19, 0x44, 0xc4, 0x73, 0xcd, 0x5e, 0x63, 0xa5, 0x8e, 0x9c, 0xb7, 0x5c, 0xc4, 0x81, 0x51, 0x37,
0x28, 0xfd, 0x0a, 0x70, 0xd8, 0x96, 0xb7, 0xce, 0x6f, 0x1c, 0xe4, 0xc8, 0x6a, 0x3d, 0x4e, 0x40,
0x2d, 0x22, 0xf7, 0xc0, 0xcf, 0xdb, 0xbe, 0x91, 0x7a, 0x18, 0x8f, 0x1a, 0xa0, 0x58, 0x51, 0x35,
0x39, 0xea, 0x51, 0x3c, 0x6a, 0x40, 0xfa, 0xcb, 0x81, 0x19, 0xc5, 0xbc, 0xe5, 0xc5, 0xdf, 0xe6,
0x79, 0x9b, 0xe6, 0xbd, 0x86, 0xa0, 0x46, 0xc9, 0x0a, 0x26, 0x59, 0xec, 0xee, 0x79, 0xcb, 0xad,
0xfd, 0xfb, 0x99, 0x79, 0x98, 0x7d, 0xb1, 0xfc, 0xc7, 0x46, 0xf2, 0x35, 0xbd, 0x2e, 0x53, 0x9d,
0xd7, 0x28, 0x04, 0x2b, 0x8d, 0xad, 0x21, 0x1d, 0x60, 0xf2, 0x1e, 0x16, 0xa3, 0x47, 0x64, 0x07,
0xbc, 0x6f, 0xb8, 0xb6, 0x03, 0xaa, 0xa3, 0x6a, 0xf7, 0x8a, 0x5d, 0xf6, 0xa8, 0x67, 0x0b, 0xa9,
0x01, 0xef, 0xdc, 0xb7, 0x4e, 0xfa, 0x04, 0xb6, 0x4f, 0x39, 0xcb, 0x71, 0x30, 0x28, 0x02, 0xb7,
0x2a, 0xec, 0x53, 0xb7, 0x2a, 0xd2, 0x97, 0xb0, 0xb0, 0xf7, 0x36, 0x15, 0x0f, 0xc1, 0x17, 0x1d,
0x6b, 0x54, 0xd0, 0x54, 0xdf, 0x7e, 0x76, 0xd2, 0xb1, 0x86, 0x1a, 0x2e, 0xfd, 0xe1, 0xc2, 0x54,
0x61, 0xf5, 0x83, 0x52, 0x3d, 0xb3, 0x4a, 0x06, 0x58, 0x71, 0x77, 0x10, 0x57, 0x9e, 0x77, 0x8c,
0xa3, 0x35, 0x37, 0xa4, 0x16, 0x11, 0x02, 0xd3, 0x86, 0xd5, 0xc6, 0xdc, 0x90, 0xea, 0xf3, 0x66,
0xde, 0xfc, 0x71, 0xde, 0x12, 0x08, 0x8a, 0x9e, 0x33, 0x59, 0xb5, 0x8d, 0xcd, 0xca, 0x35, 0x26,
0xab, 0x0d, 0xa3, 0xe7, 0xba, 0xe1, 0xbb, 0xba, 0xe1, 0x7f, 0xda, 0xfc, 0x18, 0xa6, 0x72, 0xdd,
0xa1, 0x0e, 0x51, 0xb4, 0x1f, 0xea, 0xe2, 0xd3, 0x75, 0x87, 0x54, 0xd3, 0xff, 0xe5, 0xf5, 0xf3,
0xa7, 0x10, 0x0c, 0x72, 0x64, 0x0b, 0xe6, 0x9f, 0x8f, 0x3e, 0x1c, 0x9f, 0x1d, 0x1d, 0xec, 0x4c,
0xc8, 0x36, 0x04, 0xc7, 0x67, 0xa7, 0x06, 0x39, 0xfb, 0x3f, 0x1d, 0xf0, 0x0f, 0xd4, 0x62, 0x20,
0xbb, 0xe0, 0x1d, 0xb6, 0x25, 0xd9, 0xca, 0x6e, 0x12, 0x9c, 0xcc, 0x6d, 0x50, 0xd2, 0xc9, 0x2b,
0x87, 0xbc, 0x80, 0x99, 0x59, 0x04, 0x24, 0xca, 0x46, 0xcb, 0x23, 0xb9, 0x93, 0x8d, 0x37, 0x44,
0x3a, 0x21, 0x4b, 0xf0, 0xf5, 0x07, 0x4e, 0x16, 0xd9, 0xe6, 0x4e, 0x48, 0xa2, 0x6c, 0xf4, 0xdd,
0x9b, 0x4a, 0xfd, 0xa7, 0x93, 0x45, 0xb6, 0x19, 0x8e, 0x24, 0xca, 0x46, 0x59, 0x48, 0x27, 0xe7,
0x33, 0xbd, 0xbb, 0xde, 0xfc, 0x09, 0x00, 0x00, 0xff, 0xff, 0x6e, 0x42, 0x7f, 0x05, 0xde, 0x04,
0x00, 0x00,
// 646 bytes of a gzipped FileDescriptorProto
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xa4, 0x54, 0xdb, 0x6e, 0xd3, 0x4a,
0x14, 0x8d, 0xed, 0x38, 0xb1, 0x77, 0x62, 0x9f, 0x6a, 0xce, 0x45, 0x96, 0x0f, 0xd0, 0xca, 0x12,
0x52, 0xb8, 0x68, 0x02, 0xe1, 0x85, 0xcb, 0x1b, 0x14, 0x09, 0xa4, 0xd2, 0x4a, 0xd3, 0x96, 0xf7,
0xa9, 0x3d, 0x4a, 0x03, 0xf5, 0x85, 0x99, 0x71, 0xa5, 0xbc, 0xf0, 0x23, 0xfc, 0x04, 0xff, 0x82,
0xf8, 0x1f, 0x34, 0x17, 0xb7, 0xb6, 0x10, 0xaa, 0x10, 0x6f, 0x5e, 0x6b, 0xaf, 0xd9, 0xd9, 0x7b,
0x69, 0x65, 0xc3, 0x6e, 0xc1, 0xce, 0xda, 0xf5, 0x52, 0x30, 0x7e, 0xb9, 0xc9, 0xd9, 0xb2, 0xe1,
0xb5, 0xac, 0x97, 0x9a, 0xc3, 0xfa, 0x3b, 0xbb, 0x07, 0xd1, 0x1b, 0x46, 0x2f, 0xe4, 0x39, 0x61,
0x9f, 0x5a, 0x26, 0x24, 0x4a, 0x60, 0x6a, 0xd5, 0x89, 0xb3, 0xe7, 0x2c, 0x42, 0xd2, 0xc1, 0x6c,
0x01, 0x71, 0x27, 0x15, 0x4d, 0x5d, 0x09, 0x86, 0xfe, 0x83, 0x89, 0x90, 0x54, 0xb6, 0xc2, 0x4a,
0x2d, 0xca, 0x16, 0x30, 0x3f, 0x96, 0x54, 0x8a, 0x9b, 0x7b, 0x7e, 0x77, 0x20, 0xb2, 0x52, 0xdb,
0xf3, 0x16, 0x84, 0x72, 0x53, 0x32, 0x21, 0x69, 0xd9, 0x68, 0xf5, 0x98, 0x5c, 0x13, 0xba, 0x93,
0xa4, 0x5c, 0xb2, 0x22, 0x71, 0x75, 0xad, 0x83, 0x6a, 0x96, 0xb6, 0x51, 0xc2, 0xc4, 0xd3, 0x05,
0x8b, 0x14, 0x5f, 0xb2, 0xb2, 0xe6, 0xdb, 0x64, 0x6c, 0x78, 0x83, 0x54, 0x27, 0x79, 0xce, 0x19,
0x2d, 0x44, 0xe2, 0x9b, 0x4e, 0x16, 0xa2, 0x18, 0xdc, 0x75, 0x9e, 0x4c, 0x34, 0xe9, 0xae, 0x73,
0x94, 0x42, 0xc0, 0xcd, 0x22, 0x22, 0x99, 0x6a, 0xf6, 0x0a, 0xab, 0xee, 0x8c, 0xf3, 0x9a, 0x8b,
0x24, 0x30, 0xdd, 0x0d, 0xca, 0x3e, 0x00, 0x1c, 0xd4, 0xeb, 0x1b, 0xf7, 0x37, 0x0e, 0x72, 0x46,
0x4b, 0xbd, 0x4e, 0x40, 0x2c, 0x42, 0xff, 0x80, 0x9f, 0xd7, 0x6d, 0x25, 0xf5, 0x32, 0x1e, 0x31,
0x40, 0xb1, 0x62, 0x53, 0xe5, 0x4c, 0xaf, 0xe2, 0x11, 0x03, 0xb2, 0xaf, 0x0e, 0x4c, 0x08, 0xcb,
0x6b, 0x5e, 0xfc, 0x6c, 0x9e, 0xd7, 0x37, 0xef, 0x31, 0x04, 0x25, 0x93, 0xb4, 0xa0, 0x92, 0x26,
0xee, 0x9e, 0xb7, 0x98, 0xad, 0xfe, 0xc5, 0xe6, 0x21, 0x7e, 0x67, 0xf9, 0xd7, 0x95, 0xe4, 0x5b,
0x72, 0x25, 0x53, 0x93, 0x97, 0x4c, 0x08, 0xba, 0x36, 0xb6, 0x86, 0xa4, 0x83, 0xe9, 0x0b, 0x88,
0x06, 0x8f, 0xd0, 0x0e, 0x78, 0x1f, 0xd9, 0xd6, 0x2e, 0xa8, 0x3e, 0xd5, 0xb8, 0x97, 0xf4, 0xa2,
0x65, 0x7a, 0xb7, 0x90, 0x18, 0xf0, 0xdc, 0x7d, 0xea, 0x64, 0x77, 0x60, 0x7e, 0xc2, 0x69, 0xce,
0x3a, 0x83, 0x62, 0x70, 0x37, 0x85, 0x7d, 0xea, 0x6e, 0x8a, 0xec, 0x21, 0x44, 0xb6, 0x6e, 0x53,
0xf1, 0x3f, 0xf8, 0xa2, 0xa1, 0x95, 0x0a, 0x9a, 0x9a, 0xdb, 0xc7, 0xc7, 0x0d, 0xad, 0x88, 0xe1,
0xb2, 0x2f, 0x2e, 0x8c, 0x15, 0x56, 0x3f, 0x28, 0xd5, 0x33, 0xdb, 0xc9, 0x00, 0xdb, 0xdc, 0xed,
0x9a, 0x2b, 0xcf, 0x1b, 0xca, 0x99, 0x35, 0x37, 0x24, 0x16, 0x21, 0x04, 0xe3, 0x8a, 0x96, 0xc6,
0xdc, 0x90, 0xe8, 0xef, 0x7e, 0xde, 0xfc, 0x61, 0xde, 0x52, 0x08, 0x8a, 0x96, 0x53, 0xb9, 0xa9,
0x2b, 0x9b, 0x95, 0x2b, 0x8c, 0x96, 0x3d, 0xa3, 0xa7, 0x7a, 0xe0, 0xbf, 0xf5, 0xc0, 0xbf, 0xb4,
0xf9, 0x36, 0x8c, 0xe5, 0xb6, 0x61, 0x3a, 0x44, 0xf1, 0x2a, 0xd4, 0xe2, 0x93, 0x6d, 0xc3, 0x88,
0xa6, 0xff, 0xcc, 0xeb, 0x18, 0xe6, 0xaf, 0x68, 0x7e, 0xde, 0x79, 0x9d, 0x7d, 0x86, 0xc8, 0x62,
0xeb, 0xed, 0x0a, 0x26, 0x5a, 0xdd, 0x99, 0x9b, 0xe2, 0x41, 0x1d, 0xbf, 0xd7, 0x45, 0x33, 0xb2,
0x55, 0xa6, 0xcf, 0x60, 0xd6, 0xa3, 0x7f, 0x67, 0x9e, 0xfb, 0x77, 0x21, 0xe8, 0xd6, 0x43, 0x33,
0x98, 0xbe, 0x3d, 0x7c, 0x79, 0x74, 0x7a, 0xb8, 0xbf, 0x33, 0x42, 0x73, 0x08, 0x8e, 0x4e, 0x4f,
0x0c, 0x72, 0x56, 0xdf, 0x1c, 0xf0, 0xf7, 0xd5, 0xa1, 0x42, 0xbb, 0xe0, 0x1d, 0xd4, 0x6b, 0x34,
0xc3, 0xd7, 0xff, 0xa8, 0x74, 0x6a, 0x83, 0x9b, 0x8d, 0x1e, 0x39, 0xe8, 0x01, 0x4c, 0xcc, 0x61,
0x42, 0x31, 0x1e, 0x1c, 0xb3, 0xf4, 0x2f, 0x3c, 0xbc, 0x58, 0xd9, 0x08, 0x2d, 0xc0, 0xd7, 0x07,
0x07, 0x45, 0xb8, 0x7f, 0xa3, 0xd2, 0x18, 0x0f, 0xee, 0x90, 0x51, 0xea, 0x10, 0xa2, 0x08, 0xf7,
0xc3, 0x9a, 0xc6, 0x78, 0x90, 0x4d, 0xa3, 0xd4, 0x96, 0xa1, 0x08, 0xf7, 0xad, 0x4e, 0xe3, 0xa1,
0x93, 0xd9, 0xe8, 0x6c, 0xa2, 0xaf, 0xee, 0x93, 0x1f, 0x01, 0x00, 0x00, 0xff, 0xff, 0x22, 0x65,
0x99, 0x10, 0x98, 0x05, 0x00, 0x00,
}
// Reference imports to suppress errors if they are not otherwise used.
@ -657,6 +733,7 @@ type DebugClient interface {
Health(ctx context.Context, in *HealthRequest, opts ...grpc.CallOption) (*HealthResponse, error)
Stats(ctx context.Context, in *StatsRequest, opts ...grpc.CallOption) (*StatsResponse, error)
Trace(ctx context.Context, in *TraceRequest, opts ...grpc.CallOption) (*TraceResponse, error)
Cache(ctx context.Context, in *CacheRequest, opts ...grpc.CallOption) (*CacheResponse, error)
}
type debugClient struct {
@ -726,12 +803,22 @@ func (c *debugClient) Trace(ctx context.Context, in *TraceRequest, opts ...grpc.
return out, nil
}
func (c *debugClient) Cache(ctx context.Context, in *CacheRequest, opts ...grpc.CallOption) (*CacheResponse, error) {
out := new(CacheResponse)
err := c.cc.Invoke(ctx, "/Debug/Cache", in, out, opts...)
if err != nil {
return nil, err
}
return out, nil
}
// DebugServer is the server API for Debug service.
type DebugServer interface {
Log(*LogRequest, Debug_LogServer) error
Health(context.Context, *HealthRequest) (*HealthResponse, error)
Stats(context.Context, *StatsRequest) (*StatsResponse, error)
Trace(context.Context, *TraceRequest) (*TraceResponse, error)
Cache(context.Context, *CacheRequest) (*CacheResponse, error)
}
// UnimplementedDebugServer can be embedded to have forward compatible implementations.
@ -750,6 +837,9 @@ func (*UnimplementedDebugServer) Stats(ctx context.Context, req *StatsRequest) (
func (*UnimplementedDebugServer) Trace(ctx context.Context, req *TraceRequest) (*TraceResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method Trace not implemented")
}
func (*UnimplementedDebugServer) Cache(ctx context.Context, req *CacheRequest) (*CacheResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method Cache not implemented")
}
func RegisterDebugServer(s *grpc.Server, srv DebugServer) {
s.RegisterService(&_Debug_serviceDesc, srv)
@ -830,6 +920,24 @@ func _Debug_Trace_Handler(srv interface{}, ctx context.Context, dec func(interfa
return interceptor(ctx, in, info, handler)
}
func _Debug_Cache_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(CacheRequest)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(DebugServer).Cache(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: "/Debug/Cache",
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(DebugServer).Cache(ctx, req.(*CacheRequest))
}
return interceptor(ctx, in, info, handler)
}
var _Debug_serviceDesc = grpc.ServiceDesc{
ServiceName: "Debug",
HandlerType: (*DebugServer)(nil),
@ -846,6 +954,10 @@ var _Debug_serviceDesc = grpc.ServiceDesc{
MethodName: "Trace",
Handler: _Debug_Trace_Handler,
},
{
MethodName: "Cache",
Handler: _Debug_Cache_Handler,
},
},
Streams: []grpc.StreamDesc{
{

View File

@ -46,6 +46,7 @@ type DebugService interface {
Health(ctx context.Context, in *HealthRequest, opts ...client.CallOption) (*HealthResponse, error)
Stats(ctx context.Context, in *StatsRequest, opts ...client.CallOption) (*StatsResponse, error)
Trace(ctx context.Context, in *TraceRequest, opts ...client.CallOption) (*TraceResponse, error)
Cache(ctx context.Context, in *CacheRequest, opts ...client.CallOption) (*CacheResponse, error)
}
type debugService struct {
@ -139,6 +140,16 @@ func (c *debugService) Trace(ctx context.Context, in *TraceRequest, opts ...clie
return out, nil
}
func (c *debugService) Cache(ctx context.Context, in *CacheRequest, opts ...client.CallOption) (*CacheResponse, error) {
req := c.c.NewRequest(c.name, "Debug.Cache", in)
out := new(CacheResponse)
err := c.c.Call(ctx, req, out, opts...)
if err != nil {
return nil, err
}
return out, nil
}
// Server API for Debug service
type DebugHandler interface {
@ -146,6 +157,7 @@ type DebugHandler interface {
Health(context.Context, *HealthRequest, *HealthResponse) error
Stats(context.Context, *StatsRequest, *StatsResponse) error
Trace(context.Context, *TraceRequest, *TraceResponse) error
Cache(context.Context, *CacheRequest, *CacheResponse) error
}
func RegisterDebugHandler(s server.Server, hdlr DebugHandler, opts ...server.HandlerOption) error {
@ -154,6 +166,7 @@ func RegisterDebugHandler(s server.Server, hdlr DebugHandler, opts ...server.Han
Health(ctx context.Context, in *HealthRequest, out *HealthResponse) error
Stats(ctx context.Context, in *StatsRequest, out *StatsResponse) error
Trace(ctx context.Context, in *TraceRequest, out *TraceResponse) error
Cache(ctx context.Context, in *CacheRequest, out *CacheResponse) error
}
type Debug struct {
debug
@ -217,3 +230,7 @@ func (h *debugHandler) Stats(ctx context.Context, in *StatsRequest, out *StatsRe
func (h *debugHandler) Trace(ctx context.Context, in *TraceRequest, out *TraceResponse) error {
return h.DebugHandler.Trace(ctx, in, out)
}
func (h *debugHandler) Cache(ctx context.Context, in *CacheRequest, out *CacheResponse) error {
return h.DebugHandler.Cache(ctx, in, out)
}

View File

@ -1,10 +1,11 @@
syntax = "proto3";
service Debug {
rpc Log(LogRequest) returns (stream Record) {};
rpc Health(HealthRequest) returns (HealthResponse) {};
rpc Stats(StatsRequest) returns (StatsResponse) {};
rpc Log(LogRequest) returns (stream Record) {};
rpc Health(HealthRequest) returns (HealthResponse) {};
rpc Stats(StatsRequest) returns (StatsResponse) {};
rpc Trace(TraceRequest) returns (TraceResponse) {};
rpc Cache(CacheRequest) returns (CacheResponse) {};
}
message HealthRequest {
@ -97,3 +98,9 @@ message Span {
map<string,string> metadata = 7;
SpanType type = 8;
}
message CacheRequest {}
message CacheResponse {
map<string, string> values = 1;
}

View File

@ -35,13 +35,14 @@ func newService(opts ...Option) Service {
// service name
serviceName := options.Server.Options().Name
// authFn returns the auth, we pass as a function since auth
// has not yet been set at this point.
// we pass functions to the wrappers since the values can change during initialisation
authFn := func() auth.Auth { return options.Server.Options().Auth }
cacheFn := func() *client.Cache { return options.Client.Options().Cache }
// wrap client to inject From-Service header on any calls
options.Client = wrapper.FromService(serviceName, options.Client)
options.Client = wrapper.TraceCall(serviceName, trace.DefaultTracer, options.Client)
options.Client = wrapper.CacheClient(cacheFn, options.Client)
options.Client = wrapper.AuthClient(authFn, options.Client)
// wrap the server to provide handler stats
@ -183,7 +184,7 @@ func (s *service) Run() error {
// register the debug handler
s.opts.Server.Handle(
s.opts.Server.NewHandler(
handler.NewHandler(),
handler.NewHandler(s.opts.Client),
server.InternalHandler(true),
),
)

View File

@ -5,6 +5,7 @@ import (
"context"
"sync"
"github.com/micro/go-micro/v2/client"
"github.com/micro/go-micro/v2/debug/service/handler"
"github.com/micro/go-micro/v2/proxy"
"github.com/micro/go-micro/v2/server"
@ -42,7 +43,7 @@ func New(name string, p proxy.Proxy) *Server {
server.DefaultRouter.Handle(
// inject the debug handler
server.DefaultRouter.NewHandler(
handler.NewHandler(),
handler.NewHandler(client.DefaultClient),
server.InternalHandler(true),
),
)

View File

@ -2,6 +2,7 @@ package wrapper
import (
"context"
"reflect"
"strings"
"github.com/micro/go-micro/v2/auth"
@ -221,7 +222,7 @@ func AuthHandler(fn func() auth.Auth) server.HandlerWrapper {
}
// Verify the caller has access to the resource
err := a.Verify(account, res, auth.VerifyNamespace(ns))
err := a.Verify(account, res, auth.VerifyContext(ctx))
if err != nil && account != nil {
return errors.Forbidden(req.Service(), "Forbidden call made to %v:%v by %v", req.Service(), req.Endpoint(), account.ID)
} else if err != nil {
@ -238,3 +239,55 @@ func AuthHandler(fn func() auth.Auth) server.HandlerWrapper {
}
}
}
type cacheWrapper struct {
cacheFn func() *client.Cache
client.Client
}
// Call executes the request. If the CacheExpiry option was set, the response will be cached using
// a hash of the metadata and request as the key.
func (c *cacheWrapper) Call(ctx context.Context, req client.Request, rsp interface{}, opts ...client.CallOption) error {
// parse the options
var options client.CallOptions
for _, o := range opts {
o(&options)
}
// if the client doesn't have a cacbe setup don't continue
cache := c.cacheFn()
if cache == nil {
return c.Client.Call(ctx, req, rsp, opts...)
}
// if the cache expiry is not set, execute the call without the cache
if options.CacheExpiry == 0 {
return c.Client.Call(ctx, req, rsp, opts...)
}
// if the response is nil don't call the cache since we can't assign the response
if rsp == nil {
return c.Client.Call(ctx, req, rsp, opts...)
}
// check to see if there is a response cached, if there is assign it
if r, ok := cache.Get(ctx, &req); ok {
val := reflect.ValueOf(rsp).Elem()
val.Set(reflect.ValueOf(r).Elem())
return nil
}
// don't cache the result if there was an error
if err := c.Client.Call(ctx, req, rsp, opts...); err != nil {
return err
}
// set the result in the cache
cache.Set(ctx, &req, rsp, options.CacheExpiry)
return nil
}
// CacheClient wraps requests with the cache wrapper
func CacheClient(cacheFn func() *client.Cache, c client.Client) client.Client {
return &cacheWrapper{cacheFn, c}
}

View File

@ -3,10 +3,13 @@ package wrapper
import (
"context"
"net/http"
"reflect"
"testing"
"time"
"github.com/micro/go-micro/v2/auth"
"github.com/micro/go-micro/v2/errors"
"github.com/micro/go-micro/v2/client"
"github.com/micro/go-micro/v2/metadata"
"github.com/micro/go-micro/v2/server"
)
@ -358,6 +361,98 @@ func TestAuthHandler(t *testing.T) {
}
if !handlerCalled {
t.Errorf("Expected the handler be called")
type testClient struct {
callCount int
callRsp interface{}
client.Client
}
func (c *testClient) Call(ctx context.Context, req client.Request, rsp interface{}, opts ...client.CallOption) error {
c.callCount++
if c.callRsp != nil {
val := reflect.ValueOf(rsp).Elem()
val.Set(reflect.ValueOf(c.callRsp).Elem())
}
return nil
}
type testRsp struct {
value string
}
func TestCacheWrapper(t *testing.T) {
req := client.NewRequest("go.micro.service.foo", "Foo.Bar", nil)
t.Run("NilCache", func(t *testing.T) {
cli := new(testClient)
w := CacheClient(func() *client.Cache {
return nil
}, cli)
// perfroming two requests should increment the call count by two indicating the cache wasn't
// used even though the WithCache option was passed.
w.Call(context.TODO(), req, nil, client.WithCache(time.Minute))
w.Call(context.TODO(), req, nil, client.WithCache(time.Minute))
if cli.callCount != 2 {
t.Errorf("Expected the client to have been called twice")
}
})
t.Run("OptionNotSet", func(t *testing.T) {
cli := new(testClient)
cache := client.NewCache()
w := CacheClient(func() *client.Cache {
return cache
}, cli)
// perfroming two requests should increment the call count by two since we didn't pass the WithCache
// option to Call.
w.Call(context.TODO(), req, nil)
w.Call(context.TODO(), req, nil)
if cli.callCount != 2 {
t.Errorf("Expected the client to have been called twice")
}
})
t.Run("OptionSet", func(t *testing.T) {
val := "foo"
cli := &testClient{callRsp: &testRsp{value: val}}
cache := client.NewCache()
w := CacheClient(func() *client.Cache {
return cache
}, cli)
// perfroming two requests should increment the call count by once since the second request should
// have used the cache. The correct value should be set on both responses and no errors should
// be returned.
rsp1 := &testRsp{}
rsp2 := &testRsp{}
err1 := w.Call(context.TODO(), req, rsp1, client.WithCache(time.Minute))
err2 := w.Call(context.TODO(), req, rsp2, client.WithCache(time.Minute))
if err1 != nil {
t.Errorf("Expected nil error, got %v", err1)
}
if err2 != nil {
t.Errorf("Expected nil error, got %v", err2)
}
if rsp1.value != val {
t.Errorf("Expected %v to be assigned to the value, got %v", val, rsp1.value)
}
if rsp2.value != val {
t.Errorf("Expected %v to be assigned to the value, got %v", val, rsp2.value)
}
if cli.callCount != 1 {
t.Errorf("Expected the client to be called 1 time, was actually called %v time(s)", cli.callCount)
}
})
}