Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Ftr: Generic invocation supports Generalizer #1315

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions common/constant/key.go
Original file line number Diff line number Diff line change
Expand Up @@ -307,3 +307,11 @@ const (
// SERVICE_DISCOVERY_KEY indicate which service discovery instance will be used
SERVICE_DISCOVERY_KEY = "service_discovery"
)

// Generic Filter

const (
GenericSerializationDefault = "true"
// disable "protobuf-json" temporarily
//GenericSerializationProtobuf = "protobuf-json"
)
File renamed without changes.
2 changes: 1 addition & 1 deletion common/rpc_service.go
Original file line number Diff line number Diff line change
Expand Up @@ -366,7 +366,7 @@ func suiteMethod(method reflect.Method) *MethodType {

// The latest return type of the method must be error.
if returnType := mtype.Out(outNum - 1); returnType != typeOfError {
logger.Warnf("the latest return type %s of method %q is not error", returnType, mname)
logger.Debugf(`"%s" method will not be exported because its last return type %v doesn't have error`, mname, returnType)
return nil
}

Expand Down
2 changes: 1 addition & 1 deletion config/config_loader.go
Original file line number Diff line number Diff line change
Expand Up @@ -171,7 +171,7 @@ func loadConsumerConfig() {

checkRegistries(consumerConfig.Registries, consumerConfig.Registry)
for key, ref := range consumerConfig.References {
if ref.Generic {
if ref.Generic != "" {
genericService := NewGenericService(key)
SetConsumerService(genericService)
}
Expand Down
6 changes: 3 additions & 3 deletions config/reference_config.go
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ type ReferenceConfig struct {
Params map[string]string `yaml:"params" json:"params,omitempty" property:"params"`
invoker protocol.Invoker
urls []*common.URL
Generic bool `yaml:"generic" json:"generic,omitempty" property:"generic"`
Generic string `yaml:"generic" json:"generic,omitempty" property:"generic"`
Sticky bool `yaml:"sticky" json:"sticky,omitempty" property:"sticky"`
RequestTimeout string `yaml:"timeout" json:"timeout,omitempty" property:"timeout"`
ForceTag bool `yaml:"force.tag" json:"force.tag,omitempty" property:"force.tag"`
Expand Down Expand Up @@ -236,7 +236,7 @@ func (c *ReferenceConfig) getURLMap() url.Values {
urlMap.Set(constant.RETRIES_KEY, c.Retries)
urlMap.Set(constant.GROUP_KEY, c.Group)
urlMap.Set(constant.VERSION_KEY, c.Version)
urlMap.Set(constant.GENERIC_KEY, strconv.FormatBool(c.Generic))
urlMap.Set(constant.GENERIC_KEY, c.Generic)
urlMap.Set(constant.ROLE_KEY, strconv.Itoa(common.CONSUMER))
urlMap.Set(constant.PROVIDED_BY, c.ProvidedBy)
urlMap.Set(constant.SERIALIZATION_KEY, c.Serialization)
Expand All @@ -262,7 +262,7 @@ func (c *ReferenceConfig) getURLMap() url.Values {

// filter
defaultReferenceFilter := constant.DEFAULT_REFERENCE_FILTERS
if c.Generic {
if c.Generic != "" {
defaultReferenceFilter = constant.GENERIC_REFERENCE_FILTERS + "," + defaultReferenceFilter
}
urlMap.Set(constant.REFERENCE_FILTER_KEY, mergeValue(consumerConfig.Filter, c.Filter, defaultReferenceFilter))
Expand Down
139 changes: 39 additions & 100 deletions filter/generic/filter.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,6 @@ package generic

import (
"context"
"reflect"
"strings"
"time"
)

import (
Expand All @@ -31,6 +28,7 @@ import (
import (
"dubbo.apache.org/dubbo-go/v3/common/constant"
"dubbo.apache.org/dubbo-go/v3/common/extension"
"dubbo.apache.org/dubbo-go/v3/common/logger"
"dubbo.apache.org/dubbo-go/v3/filter"
"dubbo.apache.org/dubbo-go/v3/protocol"
invocation2 "dubbo.apache.org/dubbo-go/v3/protocol/invocation"
Expand All @@ -42,30 +40,52 @@ func init() {
})
}

// when do a generic invoke, struct need to be map

// nolint
// Filter ensures the structs are converted to maps, this filter is for consumer
type Filter struct{}

// Invoke turns the parameters to map for generic method
func (f *Filter) Invoke(ctx context.Context, invoker protocol.Invoker, invocation protocol.Invocation) protocol.Result {
if invocation.MethodName() == constant.GENERIC && len(invocation.Arguments()) == 3 {
oldArguments := invocation.Arguments()
if isCallingToGenericService(invoker, invocation) {

mtdname := invocation.MethodName()
oldargs := invocation.Arguments()

types := make([]string, 0, len(oldargs))
args := make([]hessian.Object, 0, len(oldargs))

if oldParams, ok := oldArguments[2].([]interface{}); ok {
newParams := make([]hessian.Object, 0, len(oldParams))
for i := range oldParams {
newParams = append(newParams, hessian.Object(struct2MapAll(oldParams[i])))
// get generic info from attachments of invocation, the default value is "true"
generic := invocation.AttachmentsByKey(constant.GENERIC_KEY, constant.GenericSerializationDefault)
// get generalizer according to value in the `generic`
g := getGeneralizer(generic)

for _, arg := range oldargs {
// use the default generalizer(MapGeneralizer)
typ, err := g.GetType(arg)
if err != nil {
logger.Errorf("failed to get type, %v", err)
}
newArguments := []interface{}{
oldArguments[0],
oldArguments[1],
newParams,
obj, err := g.Generalize(arg)
if err != nil {
logger.Errorf("generalization failed, %v", err)
return invoker.Invoke(ctx, invocation)
}
newInvocation := invocation2.NewRPCInvocation(invocation.MethodName(), newArguments, invocation.Attachments())
newInvocation.SetReply(invocation.Reply())
return invoker.Invoke(ctx, newInvocation)
types = append(types, typ)
args = append(args, obj)
}

// construct a new invocation for generic call
newargs := []interface{}{
mtdname,
types,
args,
}
newivc := invocation2.NewRPCInvocation(constant.GENERIC, newargs, invocation.Attachments())
newivc.SetReply(invocation.Reply())
newivc.Attachments()[constant.GENERIC_KEY] = invoker.GetURL().GetParam(constant.GENERIC_KEY, "")

return invoker.Invoke(ctx, newivc)
} else if isMakingAGenericCall(invoker, invocation) {
invocation.Attachments()[constant.GENERIC_KEY] = invoker.GetURL().GetParam(constant.GENERIC_KEY, "")
}
return invoker.Invoke(ctx, invocation)
}
Expand All @@ -75,84 +95,3 @@ func (f *Filter) OnResponse(_ context.Context, result protocol.Result, _ protoco
_ protocol.Invocation) protocol.Result {
return result
}

func struct2MapAll(obj interface{}) interface{} {
if obj == nil {
return obj
}
t := reflect.TypeOf(obj)
v := reflect.ValueOf(obj)
if t.Kind() == reflect.Struct {
result := make(map[string]interface{}, t.NumField())
for i := 0; i < t.NumField(); i++ {
field := t.Field(i)
value := v.Field(i)
kind := value.Kind()
if kind == reflect.Struct || kind == reflect.Slice || kind == reflect.Map {
if value.CanInterface() {
tmp := value.Interface()
if _, ok := tmp.(time.Time); ok {
setInMap(result, field, tmp)
continue
}
setInMap(result, field, struct2MapAll(tmp))
}
} else {
if value.CanInterface() {
setInMap(result, field, value.Interface())
}
}
}
return result
} else if t.Kind() == reflect.Slice {
value := reflect.ValueOf(obj)
newTemps := make([]interface{}, 0, value.Len())
for i := 0; i < value.Len(); i++ {
newTemp := struct2MapAll(value.Index(i).Interface())
newTemps = append(newTemps, newTemp)
}
return newTemps
} else if t.Kind() == reflect.Map {
newTempMap := make(map[interface{}]interface{}, v.Len())
iter := v.MapRange()
for iter.Next() {
if !iter.Value().CanInterface() {
continue
}
key := iter.Key()
mapV := iter.Value().Interface()
newTempMap[convertMapKey(key)] = struct2MapAll(mapV)
}
return newTempMap
} else {
return obj
}
}

func convertMapKey(key reflect.Value) interface{} {
switch key.Kind() {
case reflect.Bool, reflect.Int, reflect.Int8,
reflect.Int16, reflect.Int32, reflect.Int64,
reflect.Uint, reflect.Uint8, reflect.Uint16,
reflect.Uint32, reflect.Uint64, reflect.Float32,
reflect.Float64, reflect.String:
return key.Interface()
default:
return key.String()
}
}

func setInMap(m map[string]interface{}, structField reflect.StructField, value interface{}) (result map[string]interface{}) {
result = m
if tagName := structField.Tag.Get("m"); tagName == "" {
result[headerAtoa(structField.Name)] = value
} else {
result[tagName] = value
}
return
}

func headerAtoa(a string) (b string) {
b = strings.ToLower(a[:1]) + a[1:]
return
}
Loading