diff --git a/go.mod b/go.mod index d5c06e7..4853a21 100644 --- a/go.mod +++ b/go.mod @@ -3,6 +3,7 @@ module github.com/smallnest/rpcx go 1.13 require ( + github.com/ChimeraCoder/gojson v1.1.0 github.com/abronan/valkeyrie v0.0.0-20190822142731-f2e1850dc905 github.com/aliyun/alibaba-cloud-sdk-go v0.0.0-20191122014323-8cc8f217b23a // indirect github.com/anacrolix/envpprof v1.0.1 // indirect @@ -27,11 +28,13 @@ require ( github.com/hashicorp/golang-lru v0.5.3 github.com/influxdata/influxdb1-client v0.0.0-20190809212627-fc22c7df067e github.com/jehiah/go-strftime v0.0.0-20171201141054-1d33003b3869 // indirect + github.com/json-iterator/go v1.1.9 github.com/juju/ratelimit v1.0.1 github.com/julienschmidt/httprouter v1.2.0 github.com/kavu/go_reuseport v1.4.0 github.com/klauspost/cpuid v1.2.1 // indirect github.com/klauspost/reedsolomon v1.9.2 // indirect + github.com/kr/pretty v0.1.0 github.com/lestrrat/go-envload v0.0.0-20180220120943-6ed08b54a570 // indirect github.com/lestrrat/go-file-rotatelogs v0.0.0-20180223000712-d3151e2a480f // indirect github.com/lestrrat/go-strftime v0.0.0-20180220042222-ba3bf9c1d042 // indirect diff --git a/go.sum b/go.sum index 191084a..202189e 100644 --- a/go.sum +++ b/go.sum @@ -1,11 +1,12 @@ cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= +github.com/ChimeraCoder/gojson v1.1.0 h1:/6S8djl/jColpJGTYniA3xrqJWuKeyEozzPtpr5L4Pw= +github.com/ChimeraCoder/gojson v1.1.0/go.mod h1:nYbTQlu6hv8PETM15J927yM0zGj3njIldp72UT1MqSw= github.com/DataDog/datadog-go v2.2.0+incompatible/go.mod h1:LButxg5PwREeZtORoXG3tL4fMGNddJ+vMq1mwgfaqoQ= github.com/RoaringBitmap/roaring v0.4.7/go.mod h1:8khRDP4HmeXns4xIj9oGrKSz7XTQiJx2zgh7AcNke4w= github.com/abronan/valkeyrie v0.0.0-20190822142731-f2e1850dc905 h1:JG0OqQLCILn6ywoXJncu+/MFTTapP3aIIDDqB593HMc= github.com/abronan/valkeyrie v0.0.0-20190822142731-f2e1850dc905/go.mod h1:hTreU6x9m2IP2h8e0TGrSzAXSCI3lxic8/JT5CMknjY= -github.com/alangpierce/go-forceexport v0.0.0-20160317203124-8f1d6941cd75/go.mod h1:uAXEEpARkRhCZfEvy/y0Jcc888f9tHCc1W7/UeEtreE= github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= github.com/aliyun/alibaba-cloud-sdk-go v0.0.0-20191122014323-8cc8f217b23a h1:Y7Aia4DpAF6Kr7RH7cquqiQ+7xjOQdkcR/FalKyjd6w= @@ -109,7 +110,6 @@ github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfb github.com/golang/mock v1.2.0 h1:28o5sBqPkBsMGnC6b4MvE2TzSr5/AT4c/1fLqVGIwlk= github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= -github.com/golang/protobuf v1.3.0/go.mod h1:Qd/q+1AKNOZr9uGQzbzCmRO6sUih6GTPZv6a1/R87v0= github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.2 h1:6nsPYzhq5kReh6QImI3k5qWzO4PEbvbIW2cwSfR/6xs= github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= @@ -190,6 +190,8 @@ github.com/json-iterator/go v1.1.5/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCV github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= github.com/json-iterator/go v1.1.7 h1:KfgG9LzI+pYjr4xvmz/5H4FXjokeP+rlHLhv3iH62Fo= github.com/json-iterator/go v1.1.7/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= +github.com/json-iterator/go v1.1.9 h1:9yzud/Ht36ygwatGx56VwCZtlI/2AD15T1X2sjSuGns= +github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= github.com/jtolds/gls v4.2.1+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo= github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= @@ -221,15 +223,8 @@ github.com/lestrrat/go-strftime v0.0.0-20180220042222-ba3bf9c1d042 h1:Bvq8AziQ5j github.com/lestrrat/go-strftime v0.0.0-20180220042222-ba3bf9c1d042/go.mod h1:TPpsiPUEh0zFL1Snz4crhMlBe60PYxRHr5oFF3rRYg0= github.com/lucas-clemente/quic-go v0.11.0 h1:R7uxGrBWWSp817cdhkrunFsOA26vadf4EI9slWzkjlQ= github.com/lucas-clemente/quic-go v0.11.0/go.mod h1:PpMmPfPKO9nKJ/psF49ESTAGQSdfXxlg1otPbEB2nOw= -github.com/lucas-clemente/quic-go v0.13.1 h1:CxtJTXQIh2aboCPk0M6vf530XOov6DZjVBiSE3nSj8s= -github.com/lucas-clemente/quic-go v0.13.1/go.mod h1:Vn3/Fb0/77b02SGhQk36KzOUmXgVpFfizUfW5WMaqyU= -github.com/marten-seemann/chacha20 v0.2.0 h1:f40vqzzx+3GdOmzQoItkLX5WLvHgPgyYqFFIO5Gh4hQ= -github.com/marten-seemann/chacha20 v0.2.0/go.mod h1:HSdjFau7GzYRj+ahFNwsO3ouVJr1HFkWoEwNDb4TMtE= -github.com/marten-seemann/qpack v0.1.0/go.mod h1:LFt1NU/Ptjip0C2CPkhimBz5CGE3WGDAUWqna+CNTrI= github.com/marten-seemann/qtls v0.2.3 h1:0yWJ43C62LsZt08vuQJDK1uC1czUc3FJeCLPoNAI4vA= github.com/marten-seemann/qtls v0.2.3/go.mod h1:xzjG7avBwGGbdZ8dTGxlBnLArsVKLvwmjgmPuiQEcYk= -github.com/marten-seemann/qtls v0.4.1 h1:YlT8QP3WCCvvok7MGEZkMldXbyqgr8oFg5/n8Gtbkks= -github.com/marten-seemann/qtls v0.4.1/go.mod h1:pxVXcHHw1pNIt8Qo0pwSYQEoZ8yYOOPXTCZLQQunvRc= github.com/marten-seemann/quic-conn v0.0.0-20190827120552-a06e62da55b7 h1:LHBrfRHvL1gANdnSvVXeEr79pJNj5H3jmODN1jeDjlI= github.com/marten-seemann/quic-conn v0.0.0-20190827120552-a06e62da55b7/go.mod h1:BTUDloYEkSYt9Yf98rSVDnIFrSJc04H3/6FfE+lsjNg= github.com/mattn/go-colorable v0.0.9 h1:UVL0vNpWh04HeJXV0KLcaT7r06gOH2l4OW6ddYRUIY4= @@ -388,8 +383,6 @@ golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACk golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4 h1:HuIa8hRrWRSrqYzx1qI49NNxhdi2PrY7gxVSq1JjLDc= golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= -golang.org/x/crypto v0.0.0-20190829043050-9756ffdc2472 h1:Gv7RPwsi3eZ2Fgewe3CBsuOebPwO27PoXzRpJPsvSSM= -golang.org/x/crypto v0.0.0-20190829043050-9756ffdc2472/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20191122220453-ac88ee75c92c h1:/nJuwDLoL/zrqY6gf57vxC+Pi+pZ8bfhpPkicO5H7W4= golang.org/x/crypto v0.0.0-20191122220453-ac88ee75c92c/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= @@ -407,7 +400,6 @@ golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73r golang.org/x/net v0.0.0-20181201002055-351d144fa1fc/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181220203305-927f97764cc3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20190228165749-92fc7df08ae7/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= @@ -435,10 +427,6 @@ golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190801041406-cbf593c0f2f3 h1:4y9KwBHBgBNwDbtu44R5o1fdOCQUEXhbk/P4A9WmJq0= golang.org/x/sys v0.0.0-20190801041406-cbf593c0f2f3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190904154756-749cb33beabd h1:DBH9mDw0zluJT/R+nGuV3jWFWLFaHyYZWD4tOT+cjn0= -golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20191127021746-63cb32ae39b2 h1:/J2nHFg1MTqaRLFO7M+J78ASNsJoz3r0cvHBPQ77fsE= -golang.org/x/sys v0.0.0-20191127021746-63cb32ae39b2/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191128015809-6d18c012aee9 h1:ZBzSG/7F4eNKz2L3GE9o300RX0Az1Bw5HF7PDraD+qU= golang.org/x/sys v0.0.0-20191128015809-6d18c012aee9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= @@ -463,7 +451,6 @@ google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9Ywl google.golang.org/appengine v1.4.0 h1:/wp5JvzpHIxhs/dumFmF7BXTf3Z+dd4uXta4kVyO508= google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= -google.golang.org/genproto v0.0.0-20180831171423-11092d34479b/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= google.golang.org/genproto v0.0.0-20191028173616-919d9bdd9fe6 h1:UXl+Zk3jqqcbEVV7ace5lrt4YdA4tXiz3f/KbmD29Vo= google.golang.org/genproto v0.0.0-20191028173616-919d9bdd9fe6/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= diff --git a/reflection/server_reflection.go b/reflection/server_reflection.go new file mode 100644 index 0000000..121cddb --- /dev/null +++ b/reflection/server_reflection.go @@ -0,0 +1,207 @@ +package reflection + +import ( + "bytes" + "context" + "fmt" + "path/filepath" + "reflect" + "strings" + "text/template" + "unicode" + "unicode/utf8" + + "github.com/ChimeraCoder/gojson" + jsoniter "github.com/json-iterator/go" +) + +var typeOfError = reflect.TypeOf((*error)(nil)).Elem() +var typeOfContext = reflect.TypeOf((*context.Context)(nil)).Elem() + +var json = jsoniter.Config{ + TagKey: "-", +}.Froze() + +// ServiceInfo service info. +type ServiceInfo struct { + Name string + PkgPath string + Methods []*MethodInfo +} + +// MethodInfo method info +type MethodInfo struct { + Name string + ReqName string + Req string + ReplyName string + Reply string +} + +var siTemplate = `package {{.PkgPath}} + +type {{.Name}} struct{} +{{$name := .Name}} +{{range .Methods}} +{{.Req}} +{{.Reply}} +type (s *{{$name}}) {{.Name}}(ctx context.Context, arg *{{.ReqName}}, reply *{{.ReplyName}}) error { + return nil +} +{{end}} +` + +func (si ServiceInfo) String() string { + tpl := template.Must(template.New("service").Parse(siTemplate)) + var buf bytes.Buffer + tpl.Execute(&buf, si) + return buf.String() +} + +type Reflection struct { + Services map[string]*ServiceInfo +} + +func New() *Reflection { + return &Reflection{ + Services: make(map[string]*ServiceInfo), + } +} +func (r *Reflection) Register(name string, rcvr interface{}, metadata string) error { + var si = &ServiceInfo{} + + val := reflect.ValueOf(rcvr) + typ := reflect.TypeOf(rcvr) + vTyp := reflect.Indirect(val).Type() + si.Name = vTyp.Name() + pkg := vTyp.PkgPath() + if strings.Index(pkg, ".") > 0 { + pkg = pkg[strings.LastIndex(pkg, ".")+1:] + } + si.PkgPath = filepath.Base(pkg) + + for m := 0; m < val.NumMethod(); m++ { + method := typ.Method(m) + mtype := method.Type + + if method.PkgPath != "" { + continue + } + if mtype.NumIn() != 4 { + continue + } + // First arg must be context.Context + ctxType := mtype.In(1) + if !ctxType.Implements(typeOfContext) { + continue + } + + // Second arg need not be a pointer. + argType := mtype.In(2) + if !isExportedOrBuiltinType(argType) { + continue + } + // Third arg must be a pointer. + replyType := mtype.In(3) + if replyType.Kind() != reflect.Ptr { + continue + } + // Reply type must be exported. + if !isExportedOrBuiltinType(replyType) { + continue + } + // Method needs one out. + if mtype.NumOut() != 1 { + + continue + } + // The return type of the method must be error. + if returnType := mtype.Out(0); returnType != typeOfError { + continue + } + + mi := &MethodInfo{} + mi.Name = method.Name + + if argType.Kind() == reflect.Ptr { + argType = argType.Elem() + } + replyType = replyType.Elem() + + mi.ReqName = argType.Name() + mi.Req = generateTypeDefination(mi.ReqName, si.PkgPath, generateJSON(argType)) + mi.ReplyName = replyType.Name() + mi.Reply = generateTypeDefination(mi.ReqName, si.PkgPath, generateJSON(replyType)) + + si.Methods = append(si.Methods, mi) + } + + if len(si.Methods) > 0 { + r.Services[si.Name] = si + } + + return nil +} + +func (r *Reflection) GetService(ctx context.Context, s string, reply *string) error { + si, ok := r.Services[s] + if !ok { + return fmt.Errorf("not found service %s", s) + } + *reply = si.String() + + return nil +} + +func (r *Reflection) GetServices(ctx context.Context, s string, reply *string) error { + + var buf bytes.Buffer + + var pkg = `package ` + + for _, si := range r.Services { + if pkg == `` { + pkg = pkg + si.PkgPath + "\n\n" + } + buf.WriteString(strings.ReplaceAll(si.String(), pkg, "")) + } + + if pkg != `package ` { + *reply = pkg + buf.String() + } + + return nil +} + +func generateTypeDefination(name, pkg string, jsonValue string) string { + if jsonValue == "" { + return "" + } + + r := strings.NewReader(jsonValue) + output, err := gojson.Generate(r, gojson.ParseJson, name, pkg, nil, false, false) + if err != nil { + fmt.Println(err) + } + rt := strings.ReplaceAll(string(output), "``", "") + return strings.ReplaceAll(rt, "package "+pkg+"\n\n", "") +} + +func generateJSON(typ reflect.Type) string { + v := reflect.New(typ).Interface() + + data, _ := json.Marshal(v) + return string(data) +} + +func isExported(name string) bool { + rune, _ := utf8.DecodeRuneInString(name) + return unicode.IsUpper(rune) +} + +func isExportedOrBuiltinType(t reflect.Type) bool { + for t.Kind() == reflect.Ptr { + t = t.Elem() + } + return isExported(t.Name()) || t.PkgPath() == "" +} diff --git a/reflection/server_reflection_test.go b/reflection/server_reflection_test.go new file mode 100644 index 0000000..f2493f7 --- /dev/null +++ b/reflection/server_reflection_test.go @@ -0,0 +1,28 @@ +package reflection + +import ( + "context" + "testing" + + "github.com/kr/pretty" + + testutils "github.com/smallnest/rpcx/_testutils" +) + +type PBArith int + +func (t *PBArith) Mul(ctx context.Context, args *testutils.ProtoArgs, reply *testutils.ProtoReply) error { + reply.C = args.A * args.B + return nil +} + +func TestReflection_Register(t *testing.T) { + r := New() + arith := PBArith(0) + err := r.Register("Arith", &arith, "") + if err != nil { + t.Fatal(err) + } + + pretty.Println(r.Services["PBArith"].String()) +}