diff --git a/common/constant/key.go b/common/constant/key.go index 6e73183adbc38f8e6b6482f0b010fb493f5fac8a..06b37cf709bc4940f6763b72e877d3aab44b2109 100644 --- a/common/constant/key.go +++ b/common/constant/key.go @@ -22,27 +22,27 @@ const ( ) const ( - GROUP_KEY = "group" - VERSION_KEY = "version" - INTERFACE_KEY = "interface" - PATH_KEY = "path" - SERVICE_KEY = "service" - METHODS_KEY = "methods" - TIMEOUT_KEY = "timeout" - CATEGORY_KEY = "category" - CHECK_KEY = "check" - ENABLED_KEY = "enabled" - SIDE_KEY = "side" - OVERRIDE_PROVIDERS_KEY = "providerAddresses" - BEAN_NAME_KEY = "bean.name" - GENERIC_KEY = "generic" - CLASSIFIER_KEY = "classifier" - TOKEN_KEY = "token" - LOCAL_ADDR = "local-addr" - REMOTE_ADDR = "remote-addr" - DUBBO_KEY = "dubbo" - RELEASE_KEY = "release" - ANYHOST_KEY = "anyhost" + GROUP_KEY = "group" + VERSION_KEY = "version" + INTERFACE_KEY = "interface" + PATH_KEY = "path" + SERVICE_KEY = "service" + METHODS_KEY = "methods" + TIMEOUT_KEY = "timeout" + CATEGORY_KEY = "category" + CHECK_KEY = "check" + ENABLED_KEY = "enabled" + SIDE_KEY = "side" + OVERRIDE_PROVIDERS_KEY = "providerAddresses" + BEAN_NAME_KEY = "bean.name" + GENERIC_KEY = "generic" + CLASSIFIER_KEY = "classifier" + TOKEN_KEY = "token" + LOCAL_ADDR = "local-addr" + REMOTE_ADDR = "remote-addr" + DEFAULT_REMOTING_TIMEOUT = 3000 + RELEASE_KEY = "release" + ANYHOST_KEY = "anyhost" ) const ( diff --git a/common/url.go b/common/url.go index e58f652a865f7a9648c80a73c3c4773258cc246f..5a3e69f266ab509881b6fdafb21926732f68aa94 100644 --- a/common/url.go +++ b/common/url.go @@ -18,6 +18,7 @@ package common import ( + "bytes" "encoding/base64" "fmt" "math" @@ -320,12 +321,15 @@ func (c URL) Key() string { // ServiceKey gets a unique key of a service. func (c URL) ServiceKey() string { - intf := c.GetParam(constant.INTERFACE_KEY, strings.TrimPrefix(c.Path, "/")) + return ServiceKey(c.GetParam(constant.INTERFACE_KEY, strings.TrimPrefix(c.Path, "/")), + c.GetParam(constant.GROUP_KEY, ""), c.GetParam(constant.VERSION_KEY, "")) +} + +func ServiceKey(intf string, group string, version string) string { if intf == "" { return "" } - var buf strings.Builder - group := c.GetParam(constant.GROUP_KEY, "") + buf := &bytes.Buffer{} if group != "" { buf.WriteString(group) buf.WriteString("/") @@ -333,7 +337,6 @@ func (c URL) ServiceKey() string { buf.WriteString(intf) - version := c.GetParam(constant.VERSION_KEY, "") if version != "" && version != "0.0.0" { buf.WriteString(":") buf.WriteString(version) diff --git a/config/application_config.go b/config/application_config.go index 16e841792c7bffaa6f847d73c336c6a43431bc49..cc02da3debd77b1693e26ee8b522330f3c2f23c9 100644 --- a/config/application_config.go +++ b/config/application_config.go @@ -41,6 +41,16 @@ func (*ApplicationConfig) Prefix() string { return constant.DUBBO + ".application." } +// nolint +func (c *ApplicationConfig) Id() string { + return "" +} + +// SetId ... +func (c *ApplicationConfig) SetId(id string) { + +} + // UnmarshalYAML unmarshals the ApplicationConfig by @unmarshal function func (c *ApplicationConfig) UnmarshalYAML(unmarshal func(interface{}) error) error { if err := defaults.Set(c); err != nil { diff --git a/protocol/dubbo/client.go b/protocol/dubbo/client.go deleted file mode 100644 index 0bd7aac90de4583cff7cc79c01134ff1b299a747..0000000000000000000000000000000000000000 --- a/protocol/dubbo/client.go +++ /dev/null @@ -1,70 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache License, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package dubbo - -import ( - "math/rand" - "time" -) - -import ( - "gopkg.in/yaml.v2" -) - -import ( - "github.com/apache/dubbo-go/common/logger" - "github.com/apache/dubbo-go/config" - "github.com/apache/dubbo-go/protocol/dubbo/impl/remoting" -) - -func init() { - - // load clientconfig from consumer_config - // default use dubbo - consumerConfig := config.GetConsumerConfig() - if consumerConfig.ApplicationConfig == nil { - return - } - protocolConf := config.GetConsumerConfig().ProtocolConf - defaultClientConfig := remoting.GetDefaultClientConfig() - if protocolConf == nil { - logger.Info("protocol_conf default use dubbo config") - } else { - dubboConf := protocolConf.(map[interface{}]interface{})[DUBBO] - if dubboConf == nil { - logger.Warnf("dubboConf is nil") - return - } - dubboConfByte, err := yaml.Marshal(dubboConf) - if err != nil { - panic(err) - } - err = yaml.Unmarshal(dubboConfByte, &defaultClientConfig) - if err != nil { - panic(err) - } - } - clientConf := &defaultClientConfig - if err := clientConf.CheckValidity(); err != nil { - logger.Warnf("[CheckValidity] error: %v", err) - return - } - remoting.SetClientConf(*clientConf) - - rand.Seed(time.Now().UnixNano()) -} diff --git a/protocol/dubbo/client_test.go b/protocol/dubbo/client_test.go deleted file mode 100644 index 51e532e658da51d625430a04ae66ccf9935b4559..0000000000000000000000000000000000000000 --- a/protocol/dubbo/client_test.go +++ /dev/null @@ -1,301 +0,0 @@ -/* -* Licensed to the Apache Software Foundation (ASF) under one or more -* contributor license agreements. See the NOTICE file distributed with -* this work for additional information regarding copyright ownership. -* The ASF licenses this file to You under the Apache License, Version 2.0 -* (the "License"); you may not use this file except in compliance with -* the License. You may obtain a copy of the License at -* -* http://www.apache.org/licenses/LICENSE-2.0 -* -* Unless required by applicable law or agreed to in writing, software -* distributed under the License is distributed on an "AS IS" BASIS, -* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -* See the License for the specific language governing permissions and -* limitations under the License. - */ - -package dubbo - -import ( - "bytes" - "context" - "sync" - "testing" - "time" -) - -import ( - hessian "github.com/apache/dubbo-go-hessian2" - perrors "github.com/pkg/errors" - "github.com/stretchr/testify/assert" -) - -import ( - "github.com/apache/dubbo-go/common" - "github.com/apache/dubbo-go/common/proxy/proxy_factory" - "github.com/apache/dubbo-go/protocol" - "github.com/apache/dubbo-go/protocol/dubbo/impl/remoting" -) - -func TestClient_CallOneway(t *testing.T) { - proto, url := InitTest(t) - - c := &remoting.Client{ - PendingResponses: new(sync.Map), - Conf: *remoting.GetClientConf(), - Opts: remoting.Options{ - ConnectTimeout: 3e9, - RequestTimeout: 6e9, - }, - } - c.Pool = remoting.NewGettyRPCClientConnPool(c, remoting.GetClientConf().PoolSize, time.Duration(int(time.Second)*remoting.GetClientConf().PoolTTL)) - - //user := &User{} - err := c.CallOneway(remoting.NewRequest("127.0.0.1:20000", url, "GetUser", []interface{}{"1", "username"}, nil)) - assert.NoError(t, err) - - // destroy - proto.Destroy() -} - -func TestClient_Call(t *testing.T) { - proto, url := InitTest(t) - - c := &remoting.Client{ - PendingResponses: new(sync.Map), - Conf: *remoting.GetClientConf(), - Opts: remoting.Options{ - ConnectTimeout: 3e9, - RequestTimeout: 10e9, - }, - } - c.Pool = remoting.NewGettyRPCClientConnPool(c, remoting.GetClientConf().PoolSize, time.Duration(int(time.Second)*remoting.GetClientConf().PoolTTL)) - - var ( - user *User - err error - ) - - user = &User{} - err = c.Call(remoting.NewRequest("127.0.0.1:20000", url, "GetBigPkg", []interface{}{nil}, nil), remoting.NewResponse(user, nil)) - assert.NoError(t, err) - assert.NotEqual(t, "", user.Id) - assert.NotEqual(t, "", user.Name) - - user = &User{} - err = c.Call(remoting.NewRequest("127.0.0.1:20000", url, "GetUser", []interface{}{"1", "username"}, nil), remoting.NewResponse(user, nil)) - assert.NoError(t, err) - assert.Equal(t, User{Id: "1", Name: "username"}, *user) - - user = &User{} - err = c.Call(remoting.NewRequest("127.0.0.1:20000", url, "GetUser0", []interface{}{"1", nil, "username"}, nil), remoting.NewResponse(user, nil)) - assert.NoError(t, err) - assert.Equal(t, User{Id: "1", Name: "username"}, *user) - - err = c.Call(remoting.NewRequest("127.0.0.1:20000", url, "GetUser1", []interface{}{}, nil), remoting.NewResponse(user, nil)) - assert.NoError(t, err) - - err = c.Call(remoting.NewRequest("127.0.0.1:20000", url, "GetUser2", []interface{}{}, nil), remoting.NewResponse(user, nil)) - assert.EqualError(t, err, "error") - - user2 := []interface{}{} - err = c.Call(remoting.NewRequest("127.0.0.1:20000", url, "GetUser3", []interface{}{}, nil), remoting.NewResponse(&user2, nil)) - assert.NoError(t, err) - assert.Equal(t, &User{Id: "1", Name: "username"}, user2[0]) - - user2 = []interface{}{} - err = c.Call(remoting.NewRequest("127.0.0.1:20000", url, "GetUser4", []interface{}{[]interface{}{"1", "username"}}, nil), remoting.NewResponse(&user2, nil)) - assert.NoError(t, err) - assert.Equal(t, &User{Id: "1", Name: "username"}, user2[0]) - - user3 := map[interface{}]interface{}{} - err = c.Call(remoting.NewRequest("127.0.0.1:20000", url, "GetUser5", []interface{}{map[interface{}]interface{}{"id": "1", "name": "username"}}, nil), remoting.NewResponse(&user3, nil)) - assert.NoError(t, err) - assert.NotNil(t, user3) - assert.Equal(t, &User{Id: "1", Name: "username"}, user3["key"]) - - user = &User{} - err = c.Call(remoting.NewRequest("127.0.0.1:20000", url, "GetUser6", []interface{}{0}, nil), remoting.NewResponse(user, nil)) - assert.NoError(t, err) - assert.Equal(t, User{Id: "", Name: ""}, *user) - - user = &User{} - err = c.Call(remoting.NewRequest("127.0.0.1:20000", url, "GetUser6", []interface{}{1}, nil), remoting.NewResponse(user, nil)) - assert.NoError(t, err) - assert.Equal(t, User{Id: "1", Name: ""}, *user) - - // destroy - proto.Destroy() -} - -func TestClient_AsyncCall(t *testing.T) { - proto, url := InitTest(t) - - c := &remoting.Client{ - PendingResponses: new(sync.Map), - Conf: *remoting.GetClientConf(), - Opts: remoting.Options{ - ConnectTimeout: 3e9, - RequestTimeout: 6e9, - }, - } - c.Pool = remoting.NewGettyRPCClientConnPool(c, remoting.GetClientConf().PoolSize, time.Duration(int(time.Second)*remoting.GetClientConf().PoolTTL)) - - user := &User{} - lock := sync.Mutex{} - lock.Lock() - err := c.AsyncCall(remoting.NewRequest("127.0.0.1:20000", url, "GetUser", []interface{}{"1", "username"}, nil), func(response common.CallbackResponse) { - r := response.(remoting.AsyncCallbackResponse) - assert.Equal(t, User{Id: "1", Name: "username"}, *r.Reply.(*remoting.Response).Reply.(*User)) - lock.Unlock() - }, remoting.NewResponse(user, nil)) - assert.NoError(t, err) - assert.Equal(t, User{}, *user) - - // destroy - lock.Lock() - proto.Destroy() - lock.Unlock() -} - -func InitTest(t *testing.T) (protocol.Protocol, common.URL) { - - hessian.RegisterPOJO(&User{}) - - methods, err := common.ServiceMap.Register("com.ikurento.user.UserProvider", "dubbo", &UserProvider{}) - assert.NoError(t, err) - assert.Equal(t, "GetBigPkg,GetUser,GetUser0,GetUser1,GetUser2,GetUser3,GetUser4,GetUser5,GetUser6", methods) - - // config - remoting.SetClientConf(remoting.ClientConfig{ - ConnectionNum: 2, - HeartbeatPeriod: "5s", - SessionTimeout: "20s", - PoolTTL: 600, - PoolSize: 64, - GettySessionParam: remoting.GettySessionParam{ - CompressEncoding: false, - TcpNoDelay: true, - TcpKeepAlive: true, - KeepAlivePeriod: "120s", - TcpRBufSize: 262144, - TcpWBufSize: 65536, - PkgWQSize: 512, - TcpReadTimeout: "4s", - TcpWriteTimeout: "5s", - WaitTimeout: "1s", - MaxMsgLen: 10240000000, - SessionName: "client", - }, - }) - assert.NoError(t, remoting.GetClientConf().CheckValidity()) - remoting.SetServerConfig(remoting.ServerConfig{ - SessionNumber: 700, - SessionTimeout: "20s", - GettySessionParam: remoting.GettySessionParam{ - CompressEncoding: false, - TcpNoDelay: true, - TcpKeepAlive: true, - KeepAlivePeriod: "120s", - TcpRBufSize: 262144, - TcpWBufSize: 65536, - PkgWQSize: 512, - TcpReadTimeout: "1s", - TcpWriteTimeout: "5s", - WaitTimeout: "1s", - MaxMsgLen: 10240000000, - SessionName: "server", - }}) - assert.NoError(t, remoting.GetServerConfig().CheckValidity()) - - // Export - proto := GetProtocol() - url, err := common.NewURL("dubbo://127.0.0.1:20000/UserProvider?anyhost=true&" + - "application=BDTService&category=providers&default.timeout=10000&dubbo=dubbo-provider-golang-1.0.0&" + - "environment=dev&interface=com.ikurento.user.UserProvider&ip=192.168.56.1&methods=GetUser%2C&" + - "module=dubbogo+user-info+server&org=ikurento.com&owner=ZX&pid=1447&revision=0.0.1&" + - "side=provider&timeout=3000×tamp=1556509797245&bean.name=UserProvider") - assert.NoError(t, err) - proto.Export(&proxy_factory.ProxyInvoker{ - BaseInvoker: *protocol.NewBaseInvoker(url), - }) - - time.Sleep(time.Second * 2) - - return proto, url -} - -////////////////////////////////// -// provider -////////////////////////////////// - -type ( - User struct { - Id string `json:"id"` - Name string `json:"name"` - } - - UserProvider struct { - user map[string]User - } -) - -// size:4801228 -func (u *UserProvider) GetBigPkg(ctx context.Context, req []interface{}, rsp *User) error { - argBuf := new(bytes.Buffer) - for i := 0; i < 4000; i++ { - argBuf.WriteString("鍑婚紦鍏堕晽锛岃笂璺冪敤鍏点€傚湡鍥藉煄婕曪紝鎴戠嫭鍗楄銆備粠瀛欏瓙浠诧紝骞抽檲涓庡畫銆備笉鎴戜互褰掞紝蹇у績鏈夊俊銆傜埌灞呯埌澶勶紵鐖颁抚鍏堕┈锛熶簬浠ユ眰涔嬶紵浜庢灄涔嬩笅銆傛鐢熷闃旓紝涓庡瓙鎴愯銆傛墽瀛愪箣鎵嬶紝涓庡瓙鍋曡€併€備簬鍡熼様鍏紝涓嶆垜娲诲叜銆備簬鍡熸吹鍏紝涓嶆垜淇″叜銆�") - argBuf.WriteString("鍑婚紦鍏堕晽锛岃笂璺冪敤鍏点€傚湡鍥藉煄婕曪紝鎴戠嫭鍗楄銆備粠瀛欏瓙浠诧紝骞抽檲涓庡畫銆備笉鎴戜互褰掞紝蹇у績鏈夊俊銆傜埌灞呯埌澶勶紵鐖颁抚鍏堕┈锛熶簬浠ユ眰涔嬶紵浜庢灄涔嬩笅銆傛鐢熷闃旓紝涓庡瓙鎴愯銆傛墽瀛愪箣鎵嬶紝涓庡瓙鍋曡€併€備簬鍡熼様鍏紝涓嶆垜娲诲叜銆備簬鍡熸吹鍏紝涓嶆垜淇″叜銆�") - } - rsp.Id = argBuf.String() - rsp.Name = argBuf.String() - return nil -} - -func (u *UserProvider) GetUser(ctx context.Context, req []interface{}, rsp *User) error { - rsp.Id = req[0].(string) - rsp.Name = req[1].(string) - return nil -} - -func (u *UserProvider) GetUser0(id string, k *User, name string) (User, error) { - return User{Id: id, Name: name}, nil -} - -func (u *UserProvider) GetUser1() error { - return nil -} - -func (u *UserProvider) GetUser2() error { - return perrors.New("error") -} - -func (u *UserProvider) GetUser3(rsp *[]interface{}) error { - *rsp = append(*rsp, User{Id: "1", Name: "username"}) - return nil -} - -func (u *UserProvider) GetUser4(ctx context.Context, req []interface{}) ([]interface{}, error) { - - return []interface{}{User{Id: req[0].([]interface{})[0].(string), Name: req[0].([]interface{})[1].(string)}}, nil -} - -func (u *UserProvider) GetUser5(ctx context.Context, req []interface{}) (map[interface{}]interface{}, error) { - return map[interface{}]interface{}{"key": User{Id: req[0].(map[interface{}]interface{})["id"].(string), Name: req[0].(map[interface{}]interface{})["name"].(string)}}, nil -} - -func (u *UserProvider) GetUser6(id int64) (*User, error) { - if id == 0 { - return nil, nil - } - return &User{Id: "1"}, nil -} - -func (u *UserProvider) Reference() string { - return "UserProvider" -} - -func (u User) JavaClassName() string { - return "com.ikurento.user.User" -} diff --git a/protocol/dubbo/dubbo_codec.go b/protocol/dubbo/dubbo_codec.go new file mode 100644 index 0000000000000000000000000000000000000000..e575c797b3ae2096dd015ccd1c7f7dcb9df6d368 --- /dev/null +++ b/protocol/dubbo/dubbo_codec.go @@ -0,0 +1,366 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package dubbo + +import ( + "bytes" + "github.com/apache/dubbo-go/protocol/dubbo/impl" + "strconv" + "time" +) + +import ( + hessian "github.com/apache/dubbo-go-hessian2" + perrors "github.com/pkg/errors" +) + +import ( + "github.com/apache/dubbo-go/common/constant" + "github.com/apache/dubbo-go/common/logger" + "github.com/apache/dubbo-go/protocol" + "github.com/apache/dubbo-go/protocol/invocation" + "github.com/apache/dubbo-go/remoting" +) + +//SerialID serial ID +type SerialID byte + +const ( + // S_Dubbo dubbo serial id + S_Dubbo SerialID = 2 +) + +func init() { + codec := &DubboCodec{} + // this is for registry dubboCodec of dubbo protocol + remoting.RegistryCodec("dubbo", codec) +} + +// DubboPackage is for hessian encode/decode. If we refactor hessian, it will also be refactored. +//type DubboPackage struct { +// Header hessian.DubboHeader +// Service hessian.Service +// Body interface{} +// Err error +//} + +// String of DubboPackage +//func (p DubboPackage) String() string { +// return fmt.Sprintf("DubboPackage: Header-%v, Path-%v, Body-%v", p.Header, p.Service, p.Body) +//} + +// nolint +//func (p *DubboPackage) Marshal() (*bytes.Buffer, error) { +// codec := hessian.NewHessianCodec(nil) +// +// pkg, err := codec.Write(p.Service, p.Header, p.Body) +// if err != nil { +// return nil, perrors.WithStack(err) +// } +// +// return bytes.NewBuffer(pkg), nil +//} + +// nolint +//func (p *DubboPackage) Unmarshal(buf *bytes.Buffer, resp *remoting.Response) error { +// // fix issue https://github.com/apache/dubbo-go/issues/380 +// bufLen := buf.Len() +// if bufLen < hessian.HEADER_LENGTH { +// return perrors.WithStack(hessian.ErrHeaderNotEnough) +// } +// +// codec := hessian.NewHessianCodec(bufio.NewReaderSize(buf, bufLen)) +// +// // read header +// err := codec.ReadHeader(&p.Header) +// if err != nil { +// return perrors.WithStack(err) +// } +// +// if resp != nil { // for client +// if (p.Header.Type & hessian.PackageRequest) != 0x00 { +// // size of this array must be '7' +// // https://github.com/apache/dubbo-go-hessian2/blob/master/request.go#L272 +// p.Body = make([]interface{}, 7) +// } else { +// pendingRsp := remoting.GetPendingResponse(remoting.SequenceType(p.Header.ID)) +// if pendingRsp == nil { +// return perrors.Errorf("client.GetPendingResponse(%v) = nil", p.Header.ID) +// } +// p.Body = &hessian.Response{RspObj: pendingRsp.Reply} +// } +// } +// // read body +// err = codec.ReadBody(p.Body) +// return perrors.WithStack(err) +//} + +// DubboCodec. It is implements remoting.Codec +type DubboCodec struct { +} + +// encode request for transport +func (c *DubboCodec) EncodeRequest(request *remoting.Request) (*bytes.Buffer, error) { + if request.Event { + return c.encodeHeartbeartReqeust(request) + } + + invoc, ok := request.Data.(*protocol.Invocation) + if !ok { + logger.Errorf("encode request failed for parameter type :%+v", request) + return nil, perrors.Errorf("encode request failed for parameter type :%+v", request) + } + invocation := *invoc + + svc := impl.Service{} + svc.Path = invocation.AttachmentsByKey(constant.PATH_KEY, "") + svc.Interface = invocation.AttachmentsByKey(constant.INTERFACE_KEY, "") + svc.Version = invocation.AttachmentsByKey(constant.VERSION_KEY, "") + svc.Group = invocation.AttachmentsByKey(constant.GROUP_KEY, "") + svc.Method = invocation.MethodName() + timeout, err := strconv.Atoi(invocation.AttachmentsByKey(constant.TIMEOUT_KEY, strconv.Itoa(constant.DEFAULT_REMOTING_TIMEOUT))) + if err != nil { + // it will be wrapped in readwrite.Write . + return nil, perrors.WithStack(err) + } + svc.Timeout = time.Duration(timeout) + + header := impl.DubboHeader{} + serialization := invocation.AttachmentsByKey(constant.SERIALIZATION_KEY, constant.HESSIAN2_SERIALIZATION) + if serialization == constant.HESSIAN2_SERIALIZATION { + header.SerialID = constant.S_Hessian2 + } else if serialization == constant.PROTOBUF_SERIALIZATION { + header.SerialID = constant.S_Proto + } + header.ID = request.ID + if request.TwoWay { + header.Type = impl.PackageRequest_TwoWay + } else { + header.Type = impl.PackageRequest + } + + pkg := &impl.DubboPackage{ + Header: header, + Service: svc, + Body: impl.NewRequestPayload(invocation.Arguments(), invocation.Attachments()), + Err: nil, + Codec: impl.NewDubboCodec(nil), + } + + if err := impl.LoadSerializer(pkg); err != nil { + return nil, perrors.WithStack(err) + } + + return pkg.Marshal() +} + +// encode heartbeart request +func (c *DubboCodec) encodeHeartbeartReqeust(request *remoting.Request) (*bytes.Buffer, error) { + header := impl.DubboHeader{ + Type: impl.PackageHeartbeat, + SerialID: constant.S_Hessian2, + ID: request.ID, + } + + pkg := &impl.DubboPackage{ + Header: header, + Service: impl.Service{}, + Body: impl.NewRequestPayload([]interface{}{}, nil), + Err: nil, + Codec: impl.NewDubboCodec(nil), + } + + if err := impl.LoadSerializer(pkg); err != nil { + return nil, err + } + return pkg.Marshal() +} + +// encode response +func (c *DubboCodec) EncodeResponse(response *remoting.Response) (*bytes.Buffer, error) { + var ptype = impl.PackageResponse + if response.IsHeartbeat() { + ptype = impl.PackageHeartbeat + } + resp := &impl.DubboPackage{ + Header: impl.DubboHeader{ + SerialID: response.SerialID, + Type: ptype, + ID: response.ID, + ResponseStatus: response.Status, + }, + } + if !response.IsHeartbeat() { + resp.Body = &hessian.Response{ + RspObj: response.Result.(protocol.RPCResult).Rest, + Exception: response.Result.(protocol.RPCResult).Err, + Attachments: response.Result.(protocol.RPCResult).Attrs, + } + } + + codec := impl.NewDubboCodec(nil) + + pkg, err := codec.Encode(*resp) + if err != nil { + return nil, perrors.WithStack(err) + } + + return bytes.NewBuffer(pkg), nil +} + +// Decode data, including request and response. +func (c *DubboCodec) Decode(data []byte) (remoting.DecodeResult, int, error) { + if c.isRequest(data) { + req, len, err := c.decodeRequest(data) + if err != nil { + return remoting.DecodeResult{}, len, perrors.WithStack(err) + } + return remoting.DecodeResult{IsRequest: true, Result: req}, len, perrors.WithStack(err) + } else { + resp, len, err := c.decodeResponse(data) + if err != nil { + return remoting.DecodeResult{}, len, perrors.WithStack(err) + } + return remoting.DecodeResult{IsRequest: false, Result: resp}, len, perrors.WithStack(err) + } +} + +func (c *DubboCodec) isRequest(data []byte) bool { + if data[2]&byte(0x80) == 0x00 { + return false + } + return true +} + +// decode request +func (c *DubboCodec) decodeRequest(data []byte) (*remoting.Request, int, error) { + var request *remoting.Request = nil + buf := bytes.NewBuffer(data) + pkg := impl.NewDubboPackage(buf) + pkg.SetBody(make([]interface{}, 7)) + err := pkg.Unmarshal() + if err != nil { + originErr := perrors.Cause(err) + if originErr == hessian.ErrHeaderNotEnough || originErr == hessian.ErrBodyNotEnough { + //FIXME + return nil, 0, originErr + } + logger.Errorf("pkg.Unmarshal(len(@data):%d) = error:%+v", buf.Len(), err) + + return request, 0, perrors.WithStack(err) + } + request = &remoting.Request{ + ID: pkg.Header.ID, + SerialID: pkg.Header.SerialID, + TwoWay: pkg.Header.Type&impl.PackageRequest_TwoWay != 0x00, + Event: pkg.Header.Type&impl.PackageHeartbeat != 0x00, + } + if (pkg.Header.Type & impl.PackageHeartbeat) == 0x00 { + // convert params of request + req := pkg.Body.([]interface{}) // length of body should be 7 + if len(req) > 0 { + //invocation := request.Data.(*invocation.RPCInvocation) + var methodName string + var args []interface{} + var attachments map[string]string = make(map[string]string) + if req[0] != nil { + //dubbo version + request.Version = req[0].(string) + } + if req[1] != nil { + //path + attachments[constant.PATH_KEY] = req[1].(string) + } + if req[2] != nil { + //version + attachments[constant.VERSION_KEY] = req[2].(string) + } + if req[3] != nil { + //method + methodName = req[3].(string) + } + if req[4] != nil { + //ignore argTypes + } + if req[5] != nil { + args = req[5].([]interface{}) + } + if req[6] != nil { + attachments = req[6].(map[string]string) + } + invoc := invocation.NewRPCInvocationWithOptions(invocation.WithAttachments(attachments), + invocation.WithArguments(args), invocation.WithMethodName(methodName)) + request.Data = invoc + } + } + return request, hessian.HEADER_LENGTH + pkg.Header.BodyLen, nil +} + +// decode response +func (c *DubboCodec) decodeResponse(data []byte) (*remoting.Response, int, error) { + buf := bytes.NewBuffer(data) + pkg := impl.NewDubboPackage(buf) + response := &remoting.Response{} + err := pkg.Unmarshal() + if err != nil { + originErr := perrors.Cause(err) + // if the data is very big, so the receive need much times. + if originErr == hessian.ErrHeaderNotEnough || originErr == hessian.ErrBodyNotEnough { + return nil, 0, originErr + } + logger.Errorf("pkg.Unmarshal(len(@data):%d) = error:%+v", buf.Len(), err) + + return nil, 0, perrors.WithStack(err) + } + response = &remoting.Response{ + ID: pkg.Header.ID, + //Version: pkg.Header., + SerialID: pkg.Header.SerialID, + Status: pkg.Header.ResponseStatus, + Event: (pkg.Header.Type & impl.PackageHeartbeat) != 0, + } + var error error + if pkg.Header.Type&impl.PackageHeartbeat != 0x00 { + if pkg.Header.Type&impl.PackageResponse != 0x00 { + logger.Debugf("get rpc heartbeat response{header: %#v, body: %#v}", pkg.Header, pkg.Body) + if pkg.Err != nil { + logger.Errorf("rpc heartbeat response{error: %#v}", pkg.Err) + error = pkg.Err + } + } else { + logger.Debugf("get rpc heartbeat request{header: %#v, service: %#v, body: %#v}", pkg.Header, pkg.Service, pkg.Body) + response.Status = hessian.Response_OK + //reply(session, p, hessian.PackageHeartbeat) + } + return response, hessian.HEADER_LENGTH + pkg.Header.BodyLen, error + } + logger.Debugf("get rpc response{header: %#v, body: %#v}", pkg.Header, pkg.Body) + rpcResult := &protocol.RPCResult{} + response.Result = rpcResult + if pkg.Header.Type&impl.PackageRequest == 0x00 { + if pkg.Err != nil { + rpcResult.Err = pkg.Err + } else if pkg.Body.(*hessian.Response).Exception != nil { + rpcResult.Err = pkg.Body.(*hessian.Response).Exception + response.Error = rpcResult.Err + } + rpcResult.Attrs = pkg.Body.(*hessian.Response).Attachments + rpcResult.Rest = pkg.Body.(*hessian.Response).RspObj + } + + return response, hessian.HEADER_LENGTH + pkg.Header.BodyLen, nil +} diff --git a/protocol/dubbo/dubbo_invoker.go b/protocol/dubbo/dubbo_invoker.go index 1907c38a3e7bd6be7d5f332372b6efe48cdb672f..e51cc5fccfe43a97de582521f7e7b8116600d39e 100644 --- a/protocol/dubbo/dubbo_invoker.go +++ b/protocol/dubbo/dubbo_invoker.go @@ -20,6 +20,7 @@ package dubbo import ( "context" "strconv" + "strings" "sync" "sync/atomic" "time" @@ -34,9 +35,10 @@ import ( "github.com/apache/dubbo-go/common" "github.com/apache/dubbo-go/common/constant" "github.com/apache/dubbo-go/common/logger" + "github.com/apache/dubbo-go/config" "github.com/apache/dubbo-go/protocol" - "github.com/apache/dubbo-go/protocol/dubbo/impl/remoting" invocation_impl "github.com/apache/dubbo-go/protocol/invocation" + "github.com/apache/dubbo-go/remoting" ) var ( @@ -47,24 +49,35 @@ var ( ) var ( - attachmentKey = []string{constant.INTERFACE_KEY, constant.GROUP_KEY, constant.TOKEN_KEY, constant.TIMEOUT_KEY} + attachmentKey = []string{constant.INTERFACE_KEY, constant.GROUP_KEY, constant.TOKEN_KEY, constant.TIMEOUT_KEY, + constant.VERSION_KEY} ) -// DubboInvoker is dubbo client invoker. +// DubboInvoker is implement of protocol.Invoker. A dubboInvoker refer to one service and ip. type DubboInvoker struct { protocol.BaseInvoker - client *remoting.Client + // the exchange layer, it is focus on network communication. + client *remoting.ExchangeClient quitOnce sync.Once + // timeout for service(interface) level. + timeout time.Duration // Used to record the number of requests. -1 represent this DubboInvoker is destroyed reqNum int64 } -// NewDubboInvoker ... -func NewDubboInvoker(url common.URL, client *remoting.Client) *DubboInvoker { +// NewDubboInvoker constructor +func NewDubboInvoker(url common.URL, client *remoting.ExchangeClient) *DubboInvoker { + requestTimeout := config.GetConsumerConfig().RequestTimeout + + requestTimeoutStr := url.GetParam(constant.TIMEOUT_KEY, config.GetConsumerConfig().Request_Timeout) + if t, err := time.ParseDuration(requestTimeoutStr); err == nil { + requestTimeout = t + } return &DubboInvoker{ BaseInvoker: *protocol.NewBaseInvoker(url), client: client, reqNum: 0, + timeout: requestTimeout, } } @@ -85,6 +98,8 @@ func (di *DubboInvoker) Invoke(ctx context.Context, invocation protocol.Invocati defer atomic.AddInt64(&(di.reqNum), -1) inv := invocation.(*invocation_impl.RPCInvocation) + // init param + inv.SetAttachments(constant.PATH_KEY, di.GetUrl().GetParam(constant.INTERFACE_KEY, "")) for _, k := range attachmentKey { if v := di.GetUrl().GetParam(k, ""); len(v) > 0 { inv.SetAttachments(k, v) @@ -105,29 +120,47 @@ func (di *DubboInvoker) Invoke(ctx context.Context, invocation protocol.Invocati logger.Errorf("ParseBool - error: %v", err) async = false } - response := remoting.NewResponse(inv.Reply(), nil) + //response := NewResponse(inv.Reply(), nil) + rest := &protocol.RPCResult{} + timeout := di.getTimeout(inv) if async { if callBack, ok := inv.CallBack().(func(response common.CallbackResponse)); ok { - result.Err = di.client.AsyncCall(remoting.NewRequest(url.Location, url, inv.MethodName(), inv.Arguments(), inv.Attachments()), callBack, response) + //result.Err = di.client.AsyncCall(NewRequest(url.Location, url, inv.MethodName(), inv.Arguments(), inv.Attachments()), callBack, response) + result.Err = di.client.AsyncRequest(&invocation, url, timeout, callBack, rest) } else { - result.Err = di.client.CallOneway(remoting.NewRequest(url.Location, url, inv.MethodName(), inv.Arguments(), inv.Attachments())) + result.Err = di.client.Send(&invocation, url, timeout) } } else { if inv.Reply() == nil { result.Err = ErrNoReply } else { - result.Err = di.client.Call(remoting.NewRequest(url.Location, url, inv.MethodName(), inv.Arguments(), inv.Attachments()), response) + result.Err = di.client.Request(&invocation, url, timeout, rest) } } if result.Err == nil { result.Rest = inv.Reply() - result.Attrs = response.Atta + result.Attrs = rest.Attrs } logger.Debugf("result.Err: %v, result.Rest: %v", result.Err, result.Rest) return &result } +// get timeout including methodConfig +func (di *DubboInvoker) getTimeout(invocation *invocation_impl.RPCInvocation) time.Duration { + var timeout = di.GetUrl().GetParam(strings.Join([]string{constant.METHOD_KEYS, invocation.MethodName(), constant.TIMEOUT_KEY}, "."), "") + if len(timeout) != 0 { + if t, err := time.ParseDuration(timeout); err == nil { + // config timeout into attachment + invocation.SetAttachments(constant.TIMEOUT_KEY, strconv.Itoa(int(t.Milliseconds()))) + return t + } + } + // set timeout into invocation at method level + invocation.SetAttachments(constant.TIMEOUT_KEY, strconv.Itoa(int(di.timeout.Milliseconds()))) + return di.timeout +} + // Destroy destroy dubbo client invoker. func (di *DubboInvoker) Destroy() { di.quitOnce.Do(func() { diff --git a/protocol/dubbo/dubbo_invoker_test.go b/protocol/dubbo/dubbo_invoker_test.go index e96e8592207a500f876490b5af1fbb2afcc43a37..9585461434a899ee3e4a7ca689a77b3a7d1b2b27 100644 --- a/protocol/dubbo/dubbo_invoker_test.go +++ b/protocol/dubbo/dubbo_invoker_test.go @@ -18,6 +18,7 @@ package dubbo import ( + "bytes" "context" "sync" "testing" @@ -25,29 +26,26 @@ import ( ) import ( + hessian "github.com/apache/dubbo-go-hessian2" "github.com/opentracing/opentracing-go" + perrors "github.com/pkg/errors" "github.com/stretchr/testify/assert" ) import ( "github.com/apache/dubbo-go/common" "github.com/apache/dubbo-go/common/constant" - "github.com/apache/dubbo-go/protocol/dubbo/impl/remoting" + "github.com/apache/dubbo-go/common/proxy/proxy_factory" + "github.com/apache/dubbo-go/protocol" "github.com/apache/dubbo-go/protocol/invocation" + "github.com/apache/dubbo-go/remoting" + "github.com/apache/dubbo-go/remoting/getty" ) func TestDubboInvoker_Invoke(t *testing.T) { proto, url := InitTest(t) - c := &remoting.Client{ - PendingResponses: new(sync.Map), - Conf: *remoting.GetClientConf(), - Opts: remoting.Options{ - ConnectTimeout: 3 * time.Second, - RequestTimeout: 6 * time.Second, - }, - } - c.Pool = remoting.NewGettyRPCClientConnPool(c, remoting.GetClientConf().PoolSize, time.Duration(int(time.Second)*remoting.GetClientConf().PoolTTL)) + c := getExchangeClient(url) invoker := NewDubboInvoker(url, c) user := &User{} @@ -59,7 +57,6 @@ func TestDubboInvoker_Invoke(t *testing.T) { res := invoker.Invoke(context.Background(), inv) assert.NoError(t, res.Error()) assert.Equal(t, User{Id: "1", Name: "username"}, *res.Result().(*User)) - assert.Equal(t, "test_value", res.Attachments()["test_key"]) // test attachments for request/response // CallOneway inv.SetAttachments(constant.ASYNC_KEY, "true") @@ -71,7 +68,9 @@ func TestDubboInvoker_Invoke(t *testing.T) { lock.Lock() inv.SetCallBack(func(response common.CallbackResponse) { r := response.(remoting.AsyncCallbackResponse) - assert.Equal(t, User{Id: "1", Name: "username"}, *r.Reply.(*remoting.Response).Reply.(*User)) + rst := *r.Reply.(*remoting.Response).Result.(*protocol.RPCResult) + assert.Equal(t, User{Id: "1", Name: "username"}, *(rst.Rest.(*User))) + //assert.Equal(t, User{ID: "1", Name: "username"}, *r.Reply.(*Response).reply.(*User)) lock.Unlock() }) res = invoker.Invoke(context.Background(), inv) @@ -93,3 +92,143 @@ func TestDubboInvoker_Invoke(t *testing.T) { proto.Destroy() lock.Unlock() } + +func InitTest(t *testing.T) (protocol.Protocol, common.URL) { + + hessian.RegisterPOJO(&User{}) + + methods, err := common.ServiceMap.Register("", "dubbo", &UserProvider{}) + assert.NoError(t, err) + assert.Equal(t, "GetBigPkg,GetUser,GetUser0,GetUser1,GetUser2,GetUser3,GetUser4,GetUser5,GetUser6", methods) + + // config + getty.SetClientConf(getty.ClientConfig{ + ConnectionNum: 2, + HeartbeatPeriod: "5s", + SessionTimeout: "20s", + PoolTTL: 600, + PoolSize: 64, + GettySessionParam: getty.GettySessionParam{ + CompressEncoding: false, + TcpNoDelay: true, + TcpKeepAlive: true, + KeepAlivePeriod: "120s", + TcpRBufSize: 262144, + TcpWBufSize: 65536, + PkgWQSize: 512, + TcpReadTimeout: "4s", + TcpWriteTimeout: "5s", + WaitTimeout: "1s", + MaxMsgLen: 10240000000, + SessionName: "client", + }, + }) + getty.SetServerConfig(getty.ServerConfig{ + SessionNumber: 700, + SessionTimeout: "20s", + GettySessionParam: getty.GettySessionParam{ + CompressEncoding: false, + TcpNoDelay: true, + TcpKeepAlive: true, + KeepAlivePeriod: "120s", + TcpRBufSize: 262144, + TcpWBufSize: 65536, + PkgWQSize: 512, + TcpReadTimeout: "1s", + TcpWriteTimeout: "5s", + WaitTimeout: "1s", + MaxMsgLen: 10240000000, + SessionName: "server", + }}) + + // Export + proto := GetProtocol() + url, err := common.NewURL("dubbo://127.0.0.1:20702/UserProvider?anyhost=true&" + + "application=BDTService&category=providers&default.timeout=10000&dubbo=dubbo-provider-golang-1.0.0&" + + "environment=dev&interface=com.ikurento.user.UserProvider&ip=192.168.56.1&methods=GetUser%2C&" + + "module=dubbogo+user-info+server&org=ikurento.com&owner=ZX&pid=1447&revision=0.0.1&" + + "side=provider&timeout=3000×tamp=1556509797245&bean.name=UserProvider") + assert.NoError(t, err) + proto.Export(&proxy_factory.ProxyInvoker{ + BaseInvoker: *protocol.NewBaseInvoker(url), + }) + + time.Sleep(time.Second * 2) + + return proto, url +} + +////////////////////////////////// +// provider +////////////////////////////////// + +type ( + User struct { + Id string `json:"id"` + Name string `json:"name"` + } + + UserProvider struct { + user map[string]User + } +) + +// size:4801228 +func (u *UserProvider) GetBigPkg(ctx context.Context, req []interface{}, rsp *User) error { + argBuf := new(bytes.Buffer) + for i := 0; i < 400; i++ { + // use chinese for test + argBuf.WriteString("鍑婚紦鍏堕晽锛岃笂璺冪敤鍏点€傚湡鍥藉煄婕曪紝鎴戠嫭鍗楄銆備粠瀛欏瓙浠诧紝骞抽檲涓庡畫銆備笉鎴戜互褰掞紝蹇у績鏈夊俊銆傜埌灞呯埌澶勶紵鐖颁抚鍏堕┈锛熶簬浠ユ眰涔嬶紵浜庢灄涔嬩笅銆傛鐢熷闃旓紝涓庡瓙鎴愯銆傛墽瀛愪箣鎵嬶紝涓庡瓙鍋曡€併€備簬鍡熼様鍏紝涓嶆垜娲诲叜銆備簬鍡熸吹鍏紝涓嶆垜淇″叜銆�") + argBuf.WriteString("鍑婚紦鍏堕晽锛岃笂璺冪敤鍏点€傚湡鍥藉煄婕曪紝鎴戠嫭鍗楄銆備粠瀛欏瓙浠诧紝骞抽檲涓庡畫銆備笉鎴戜互褰掞紝蹇у績鏈夊俊銆傜埌灞呯埌澶勶紵鐖颁抚鍏堕┈锛熶簬浠ユ眰涔嬶紵浜庢灄涔嬩笅銆傛鐢熷闃旓紝涓庡瓙鎴愯銆傛墽瀛愪箣鎵嬶紝涓庡瓙鍋曡€併€備簬鍡熼様鍏紝涓嶆垜娲诲叜銆備簬鍡熸吹鍏紝涓嶆垜淇″叜銆�") + } + rsp.Id = argBuf.String() + rsp.Name = argBuf.String() + return nil +} + +func (u *UserProvider) GetUser(ctx context.Context, req []interface{}, rsp *User) error { + rsp.Id = req[0].(string) + rsp.Name = req[1].(string) + return nil +} + +func (u *UserProvider) GetUser0(id string, k *User, name string) (User, error) { + return User{Id: id, Name: name}, nil +} + +func (u *UserProvider) GetUser1() error { + return nil +} + +func (u *UserProvider) GetUser2() error { + return perrors.New("error") +} + +func (u *UserProvider) GetUser3(rsp *[]interface{}) error { + *rsp = append(*rsp, User{Id: "1", Name: "username"}) + return nil +} + +func (u *UserProvider) GetUser4(ctx context.Context, req []interface{}) ([]interface{}, error) { + + return []interface{}{User{Id: req[0].([]interface{})[0].(string), Name: req[0].([]interface{})[1].(string)}}, nil +} + +func (u *UserProvider) GetUser5(ctx context.Context, req []interface{}) (map[interface{}]interface{}, error) { + return map[interface{}]interface{}{"key": User{Id: req[0].(map[interface{}]interface{})["id"].(string), Name: req[0].(map[interface{}]interface{})["name"].(string)}}, nil +} + +func (u *UserProvider) GetUser6(id int64) (*User, error) { + if id == 0 { + return nil, nil + } + return &User{Id: "1"}, nil +} + +func (u *UserProvider) Reference() string { + return "UserProvider" +} + +func (u User) JavaClassName() string { + return "com.ikurento.user.User" +} diff --git a/protocol/dubbo/dubbo_protocol.go b/protocol/dubbo/dubbo_protocol.go index cc67569867241256c4c1bc66126f74ac3f6173ad..915ee74bdea6745fe7c0ac731ef4e1f91b51d076 100644 --- a/protocol/dubbo/dubbo_protocol.go +++ b/protocol/dubbo/dubbo_protocol.go @@ -18,10 +18,16 @@ package dubbo import ( + "context" + "fmt" "sync" "time" ) +import ( + "github.com/opentracing/opentracing-go" +) + import ( "github.com/apache/dubbo-go/common" "github.com/apache/dubbo-go/common/constant" @@ -29,7 +35,9 @@ import ( "github.com/apache/dubbo-go/common/logger" "github.com/apache/dubbo-go/config" "github.com/apache/dubbo-go/protocol" - "github.com/apache/dubbo-go/protocol/dubbo/impl/remoting" + "github.com/apache/dubbo-go/protocol/invocation" + "github.com/apache/dubbo-go/remoting" + "github.com/apache/dubbo-go/remoting/getty" ) const ( @@ -37,6 +45,13 @@ const ( DUBBO = "dubbo" ) +var ( + // Make the connection can be shared. + // It will create one connection for one address (ip+port) + exchangeClientMap *sync.Map = new(sync.Map) + exchangeLock *sync.Map = new(sync.Map) +) + func init() { extension.SetProtocol(DUBBO, GetProtocol) } @@ -45,22 +60,24 @@ var ( dubboProtocol *DubboProtocol ) -// DubboProtocol is a dubbo protocol implement. +// It support dubbo protocol. It implements Protocol interface for dubbo protocol. type DubboProtocol struct { protocol.BaseProtocol - serverMap map[string]*remoting.Server + // It is store relationship about serviceKey(group/interface:version) and ExchangeServer + // The ExchangeServer is introduced to replace of Server. Because Server is depend on getty directly. + serverMap map[string]*remoting.ExchangeServer serverLock sync.Mutex } -// NewDubboProtocol create a dubbo protocol. +// nolint func NewDubboProtocol() *DubboProtocol { return &DubboProtocol{ BaseProtocol: protocol.NewBaseProtocol(), - serverMap: make(map[string]*remoting.Server), + serverMap: make(map[string]*remoting.ExchangeServer), } } -// Export export dubbo service. +// nolint func (dp *DubboProtocol) Export(invoker protocol.Invoker) protocol.Exporter { url := invoker.GetUrl() serviceKey := url.ServiceKey() @@ -74,18 +91,12 @@ func (dp *DubboProtocol) Export(invoker protocol.Invoker) protocol.Exporter { // Refer create dubbo service reference. func (dp *DubboProtocol) Refer(url common.URL) protocol.Invoker { - //default requestTimeout - var requestTimeout = config.GetConsumerConfig().RequestTimeout - - requestTimeoutStr := url.GetParam(constant.TIMEOUT_KEY, config.GetConsumerConfig().Request_Timeout) - if t, err := time.ParseDuration(requestTimeoutStr); err == nil { - requestTimeout = t + exchangeClient := getExchangeClient(url) + if exchangeClient == nil { + logger.Warnf("can't dial the server: %+v", url.Location) + return nil } - - invoker := NewDubboInvoker(url, remoting.NewClient(remoting.Options{ - ConnectTimeout: config.GetConsumerConfig().ConnectTimeout, - RequestTimeout: requestTimeout, - })) + invoker := NewDubboInvoker(url, exchangeClient) dp.SetInvokers(invoker) logger.Infof("Refer service: %s", url.String()) return invoker @@ -115,9 +126,12 @@ func (dp *DubboProtocol) openServer(url common.URL) { dp.serverLock.Lock() _, ok = dp.serverMap[url.Location] if !ok { - srv := remoting.NewServer(NewStubHandler()) + handler := func(invocation *invocation.RPCInvocation) protocol.RPCResult { + return doHandleRequest(invocation) + } + srv := remoting.NewExchangeServer(url, getty.NewServer(url, handler)) dp.serverMap[url.Location] = srv - srv.Start(url) + srv.Start() } dp.serverLock.Unlock() } @@ -130,3 +144,100 @@ func GetProtocol() protocol.Protocol { } return dubboProtocol } + +func doHandleRequest(rpcInvocation *invocation.RPCInvocation) protocol.RPCResult { + exporter, _ := dubboProtocol.ExporterMap().Load(rpcInvocation.ServiceKey()) + result := protocol.RPCResult{} + if exporter == nil { + err := fmt.Errorf("don't have this exporter, key: %s", rpcInvocation.ServiceKey()) + logger.Errorf(err.Error()) + result.Err = err + //reply(session, p, hessian.PackageResponse) + return result + } + invoker := exporter.(protocol.Exporter).GetInvoker() + if invoker != nil { + // FIXME + ctx := rebuildCtx(rpcInvocation) + + invokeResult := invoker.Invoke(ctx, rpcInvocation) + if err := invokeResult.Error(); err != nil { + result.Err = invokeResult.Error() + //p.Header.ResponseStatus = hessian.Response_OK + //p.Body = hessian.NewResponse(nil, err, result.Attachments()) + } else { + result.Rest = invokeResult.Result() + //p.Header.ResponseStatus = hessian.Response_OK + //p.Body = hessian.NewResponse(res, nil, result.Attachments()) + } + } else { + result.Err = fmt.Errorf("don't have the invoker, key: %s", rpcInvocation.ServiceKey()) + } + return result +} + +func getExchangeClient(url common.URL) *remoting.ExchangeClient { + clientTmp, ok := exchangeClientMap.Load(url.Location) + if !ok { + var exchangeClientTmp *remoting.ExchangeClient + func() { + // lock for NewExchangeClient and store into map. + _, loaded := exchangeLock.LoadOrStore(url.Location, 0x00) + // unlock + defer exchangeLock.Delete(url.Location) + if loaded { + // retry for 5 times. + for i := 0; i < 5; i++ { + if clientTmp, ok = exchangeClientMap.Load(url.Location); ok { + break + } else { + // if cannot get, sleep a while. + time.Sleep(time.Duration(i*100) * time.Millisecond) + } + } + return + } + // new ExchangeClient + exchangeClientTmp = remoting.NewExchangeClient(url, getty.NewClient(getty.Options{ + ConnectTimeout: config.GetConsumerConfig().ConnectTimeout, + }), config.GetConsumerConfig().ConnectTimeout, false) + // input store + if exchangeClientTmp != nil { + exchangeClientMap.Store(url.Location, exchangeClientTmp) + } + }() + if exchangeClientTmp != nil { + return exchangeClientTmp + } + } + // cannot dial the server + if clientTmp == nil { + return nil + } + exchangeClient, ok := clientTmp.(*remoting.ExchangeClient) + if !ok { + exchangeClientTmp := remoting.NewExchangeClient(url, getty.NewClient(getty.Options{ + ConnectTimeout: config.GetConsumerConfig().ConnectTimeout, + }), config.GetConsumerConfig().ConnectTimeout, false) + if exchangeClientTmp != nil { + exchangeClientMap.Store(url.Location, exchangeClientTmp) + } + return exchangeClientTmp + } + return exchangeClient +} + +// rebuildCtx rebuild the context by attachment. +// Once we decided to transfer more context's key-value, we should change this. +// now we only support rebuild the tracing context +func rebuildCtx(inv *invocation.RPCInvocation) context.Context { + ctx := context.WithValue(context.Background(), "attachment", inv.Attachments()) + + // actually, if user do not use any opentracing framework, the err will not be nil. + spanCtx, err := opentracing.GlobalTracer().Extract(opentracing.TextMap, + opentracing.TextMapCarrier(inv.Attachments())) + if err == nil { + ctx = context.WithValue(ctx, constant.TRACING_REMOTE_SPAN_CTX, spanCtx) + } + return ctx +} diff --git a/protocol/dubbo/dubbo_protocol_test.go b/protocol/dubbo/dubbo_protocol_test.go index 1915a072f0e4ccb874e2a366dba72d70da2f7a7c..07b890f1345eaf994b6d7ba90642a116526355c4 100644 --- a/protocol/dubbo/dubbo_protocol_test.go +++ b/protocol/dubbo/dubbo_protocol_test.go @@ -22,22 +22,62 @@ import ( ) import ( - "github.com/apache/dubbo-go/common" - "github.com/apache/dubbo-go/common/constant" - "github.com/apache/dubbo-go/protocol" - "github.com/apache/dubbo-go/protocol/dubbo/impl/remoting" + "github.com/stretchr/testify/assert" ) import ( - "github.com/stretchr/testify/assert" + "github.com/apache/dubbo-go/common" + "github.com/apache/dubbo-go/common/constant" + "github.com/apache/dubbo-go/protocol" + "github.com/apache/dubbo-go/remoting/getty" ) +func init() { + getty.SetServerConfig(getty.ServerConfig{ + SessionNumber: 700, + SessionTimeout: "20s", + GettySessionParam: getty.GettySessionParam{ + CompressEncoding: false, + TcpNoDelay: true, + TcpKeepAlive: true, + KeepAlivePeriod: "120s", + TcpRBufSize: 262144, + TcpWBufSize: 65536, + PkgWQSize: 512, + TcpReadTimeout: "1s", + TcpWriteTimeout: "5s", + WaitTimeout: "1s", + MaxMsgLen: 10240000000, + SessionName: "server", + }}) + getty.SetClientConf(getty.ClientConfig{ + ConnectionNum: 1, + HeartbeatPeriod: "3s", + SessionTimeout: "20s", + PoolTTL: 600, + PoolSize: 64, + GettySessionParam: getty.GettySessionParam{ + CompressEncoding: false, + TcpNoDelay: true, + TcpKeepAlive: true, + KeepAlivePeriod: "120s", + TcpRBufSize: 262144, + TcpWBufSize: 65536, + PkgWQSize: 512, + TcpReadTimeout: "4s", + TcpWriteTimeout: "5s", + WaitTimeout: "1s", + MaxMsgLen: 10240000000, + SessionName: "client", + }, + }) +} func TestDubboProtocol_Export(t *testing.T) { - srvCfg := remoting.GetDefaultServerConfig() - remoting.SetServerConfig(srvCfg) + srvCfg := getty.GetDefaultServerConfig() + getty.SetServerConfig(srvCfg) // Export proto := GetProtocol() - url, err := common.NewURL("dubbo://127.0.0.1:20000/com.ikurento.user.UserProvider?anyhost=true&" + + url, err := common.NewURL("dubbo://127.0.0.1:20094/com.ikurento.user.UserProvider?anyhost=true&" + "application=BDTService&category=providers&default.timeout=10000&dubbo=dubbo-provider-golang-1.0.0&" + "environment=dev&interface=com.ikurento.user.UserProvider&ip=192.168.56.1&methods=GetUser%2C&" + "module=dubbogo+user-info+server&org=ikurento.com&owner=ZX&pid=1447&revision=0.0.1&" + @@ -50,7 +90,7 @@ func TestDubboProtocol_Export(t *testing.T) { assert.True(t, eq) // second service: the same path and the different version - url2, err := common.NewURL("dubbo://127.0.0.1:20000/com.ikurento.user.UserProvider?anyhost=true&"+ + url2, err := common.NewURL("dubbo://127.0.0.1:20095/com.ikurento.user.UserProvider?anyhost=true&"+ "application=BDTService&category=providers&default.timeout=10000&dubbo=dubbo-provider-golang-1.0.0&"+ "environment=dev&interface=com.ikurento.user.UserProvider&ip=192.168.56.1&methods=GetUser%2C&"+ "module=dubbogo+user-info+server&org=ikurento.com&owner=ZX&pid=1447&revision=0.0.1&"+ @@ -76,12 +116,26 @@ func TestDubboProtocol_Export(t *testing.T) { assert.False(t, ok) } +func TestDubboProtocol_Refer_No_connect(t *testing.T) { + // Refer + proto := GetProtocol() + url, err := common.NewURL("dubbo://127.0.0.1:20096/com.ikurento.user.UserProvider?anyhost=true&" + + "application=BDTService&category=providers&default.timeout=10000&dubbo=dubbo-provider-golang-1.0.0&" + + "environment=dev&interface=com.ikurento.user.UserProvider&ip=192.168.56.1&methods=GetUser%2C&" + + "module=dubbogo+user-info+server&org=ikurento.com&owner=ZX&pid=1447&revision=0.0.1&" + + "side=provider&timeout=3000×tamp=1556509797245") + assert.NoError(t, err) + invoker := proto.Refer(url) + assert.Nil(t, invoker) +} + func TestDubboProtocol_Refer(t *testing.T) { - cliCfg := remoting.GetDefaultClientConfig() - remoting.SetClientConf(cliCfg) + cliCfg := getty.GetDefaultClientConfig() + getty.SetClientConf(cliCfg) // Refer proto := GetProtocol() - url, err := common.NewURL("dubbo://127.0.0.1:20000/com.ikurento.user.UserProvider?anyhost=true&" + + + url, err := common.NewURL("dubbo://127.0.0.1:20091/com.ikurento.user.UserProvider?anyhost=true&" + "application=BDTService&category=providers&default.timeout=10000&dubbo=dubbo-provider-golang-1.0.0&" + "environment=dev&interface=com.ikurento.user.UserProvider&ip=192.168.56.1&methods=GetUser%2C&" + "module=dubbogo+user-info+server&org=ikurento.com&owner=ZX&pid=1447&revision=0.0.1&" + diff --git a/protocol/dubbo/impl/codec_test.go b/protocol/dubbo/impl/codec_test.go index c93f307aa34bf159f42b33f872b3daa5dcacc5db..92f2f2e9c08324b91e220cd4b7621fd2c23a98e2 100644 --- a/protocol/dubbo/impl/codec_test.go +++ b/protocol/dubbo/impl/codec_test.go @@ -128,7 +128,7 @@ func TestDubboPackage_Protobuf_Serialization_Request(t *testing.T) { // protobuf rpc just has exact one parameter assert.Equal(t, len(req), 1) argsBytes, ok := req[0].([]byte) - assert.Equal(t, ok, true) + assert.Equal(t, true, ok) sv := pb.StringValue{} buf := proto.NewBuffer(argsBytes) err = buf.Unmarshal(&sv) diff --git a/protocol/dubbo/impl/const.go b/protocol/dubbo/impl/const.go index 06e73fab1d64354a450946542c32f7d6d4b150d7..c2f4006174a5690512a9329eaa8f541b2014197c 100644 --- a/protocol/dubbo/impl/const.go +++ b/protocol/dubbo/impl/const.go @@ -1,12 +1,3 @@ -package impl - -import ( - "reflect" - "regexp" - - "github.com/pkg/errors" -) - /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with @@ -24,6 +15,15 @@ import ( * limitations under the License. */ +package impl + +import ( + "reflect" + "regexp" + + "github.com/pkg/errors" +) + const ( DUBBO = "dubbo" ) diff --git a/protocol/dubbo/impl/proto.go b/protocol/dubbo/impl/proto.go index ea1c55da96fb71fb6c69f801ba86806187b14f8f..2622620b397b6328a540391134631cc607dd945b 100644 --- a/protocol/dubbo/impl/proto.go +++ b/protocol/dubbo/impl/proto.go @@ -21,6 +21,7 @@ import ( "bytes" "encoding/binary" "fmt" + "github.com/apache/dubbo-go/common/logger" "io" "reflect" "strconv" @@ -157,9 +158,11 @@ func unmarshalRequestProto(data []byte, pkg *DubboPackage) error { return err } arg := getRegisterMessage(argsType) - err := proto.Unmarshal(argBytes, arg.Interface().(JavaProto)) - if err != nil { - panic(err) + if !arg.IsZero() { + err := proto.Unmarshal(argBytes, arg.Interface().(JavaProto)) + if err != nil { + panic(err) + } } m := &pb.Map{} @@ -432,7 +435,8 @@ func getRegisterMessage(sig string) reflect.Value { t, ok := register.registry[sig] if !ok { - panic(fmt.Sprintf("registry dose not have for svc: %v", sig)) + logger.Error(fmt.Sprintf("registry dose not have for svc: %v", sig)) + return NilValue } return reflect.New(t) } diff --git a/protocol/dubbo/impl/remoting/client_impl.go b/protocol/dubbo/impl/remoting/client_impl.go deleted file mode 100644 index e5f7c7138b94737f9266c64082b8300f3cfdfdeb..0000000000000000000000000000000000000000 --- a/protocol/dubbo/impl/remoting/client_impl.go +++ /dev/null @@ -1,391 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache License, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package remoting - -import ( - "math/rand" - "strings" - "sync" - "time" -) - -import ( - "github.com/dubbogo/getty" - gxsync "github.com/dubbogo/gost/sync" - perrors "github.com/pkg/errors" - "go.uber.org/atomic" -) - -import ( - "github.com/apache/dubbo-go/common" - "github.com/apache/dubbo-go/common/constant" - "github.com/apache/dubbo-go/common/logger" - "github.com/apache/dubbo-go/protocol/dubbo/impl" -) - -var ( - errInvalidCodecType = perrors.New("illegal CodecType") - errInvalidAddress = perrors.New("remote address invalid or empty") - errSessionNotExist = perrors.New("session not exist") - errClientClosed = perrors.New("client closed") - errClientReadTimeout = perrors.New("client read timeout") -) - -var ( - clientConf *ClientConfig - clientGrpool *gxsync.TaskPool -) - -// SetClientConf set dubbo client config. -func SetClientConf(c ClientConfig) { - clientConf = &c - err := clientConf.CheckValidity() - if err != nil { - logger.Warnf("[ClientConfig CheckValidity] error: %v", err) - return - } - setClientGrpool() -} - -// GetClientConf get dubbo client config. -func GetClientConf() *ClientConfig { - return clientConf -} - -func setClientGrpool() { - if clientConf.GrPoolSize > 1 { - clientGrpool = gxsync.NewTaskPool(gxsync.WithTaskPoolTaskPoolSize(clientConf.GrPoolSize), gxsync.WithTaskPoolTaskQueueLength(clientConf.QueueLen), - gxsync.WithTaskPoolTaskQueueNumber(clientConf.QueueNumber)) - } -} - -// Options is option for create dubbo client -type Options struct { - // connect timeout - ConnectTimeout time.Duration - // request timeout - RequestTimeout time.Duration -} - -//AsyncCallbackResponse async response for dubbo -type AsyncCallbackResponse struct { - common.CallbackResponse - Opts Options - Cause error - Start time.Time // invoke(call) start time == write start time - ReadStart time.Time // read start time, write duration = ReadStart - Start - Reply interface{} -} - -// Client is dubbo protocol client. -type Client struct { - Opts Options - Conf ClientConfig - Pool *gettyRPCClientPool - Sequence atomic.Uint64 - - PendingResponses *sync.Map - codec impl.DubboCodec -} - -// NewClient create a new Client. -func NewClient(opt Options) *Client { - - switch { - case opt.ConnectTimeout == 0: - opt.ConnectTimeout = 3 * time.Second - fallthrough - case opt.RequestTimeout == 0: - opt.RequestTimeout = 3 * time.Second - } - - // make sure that client request Sequence is an odd number - initSequence := uint64(rand.Int63n(time.Now().UnixNano())) - if initSequence%2 == 0 { - initSequence++ - } - - c := &Client{ - Opts: opt, - PendingResponses: new(sync.Map), - Conf: *clientConf, - codec: impl.DubboCodec{}, - } - c.Sequence.Store(initSequence) - c.Pool = NewGettyRPCClientConnPool(c, clientConf.PoolSize, time.Duration(int(time.Second)*clientConf.PoolTTL)) - - return c -} - -// Request is dubbo protocol request. -type Request struct { - addr string - svcUrl common.URL - method string - args interface{} - atta map[string]string -} - -// NewRequest create a new Request. -func NewRequest(addr string, svcUrl common.URL, method string, args interface{}, atta map[string]string) *Request { - // NOTE: compatible with old versions - if svcUrl.GetParam(constant.SERIALIZATION_KEY, "") == "" { - svcUrl.SetParam(constant.SERIALIZATION_KEY, constant.DEFAULT_SERIALIZATION) - } - return &Request{ - addr: addr, - svcUrl: svcUrl, - method: method, - args: args, - atta: atta, - } -} - -// Response is dubbo protocol response. -type Response struct { - Reply interface{} - Atta map[string]string -} - -// NewResponse create a new Response. -func NewResponse(reply interface{}, atta map[string]string) *Response { - return &Response{ - Reply: reply, - Atta: atta, - } -} - -// CallOneway call one way -func (c *Client) CallOneway(request *Request) error { - - return perrors.WithStack(c.call(impl.CT_OneWay, request, NewResponse(nil, nil), nil)) -} - -// Call if @response is nil, the transport layer will get the response without notify the invoker. -func (c *Client) Call(request *Request, response *Response) error { - - ct := impl.CT_TwoWay - if response.Reply == nil { - ct = impl.CT_OneWay - } - - return perrors.WithStack(c.call(ct, request, response, nil)) -} - -// AsyncCall ... -func (c *Client) AsyncCall(request *Request, callback common.AsyncCallback, response *Response) error { - - return perrors.WithStack(c.call(impl.CT_TwoWay, request, response, callback)) -} - -func (c *Client) call(ct impl.CallType, request *Request, response *Response, callback common.AsyncCallback) error { - var ( - err error - session getty.Session - conn *gettyRPCClient - ) - conn, session, err = c.selectSession(request.addr) - if err != nil { - return perrors.WithStack(err) - } - if session == nil { - return errSessionNotExist - } - defer func() { - if err == nil { - c.Pool.put(conn) - return - } - conn.close() - }() - - var rsp *PendingResponse - svc := impl.Service{} - header := impl.DubboHeader{} - svc.Path = strings.TrimPrefix(request.svcUrl.Path, "/") - svc.Interface = request.svcUrl.GetParam(constant.INTERFACE_KEY, "") - svc.Version = request.svcUrl.GetParam(constant.VERSION_KEY, "") - svc.Group = request.svcUrl.GetParam(constant.GROUP_KEY, "") - svc.Method = request.method - svc.Timeout = c.Opts.RequestTimeout - var timeout = request.svcUrl.GetParam(strings.Join([]string{constant.METHOD_KEYS, request.method + constant.RETRIES_KEY}, "."), "") - if len(timeout) != 0 { - if t, err := time.ParseDuration(timeout); err == nil { - svc.Timeout = t - } - } - p := NewClientRequestPackage(header, svc) - - serialization := request.svcUrl.GetParam(constant.SERIALIZATION_KEY, c.Conf.Serialization) - if serialization == constant.HESSIAN2_SERIALIZATION { - p.Header.SerialID = constant.S_Hessian2 - } else if serialization == constant.PROTOBUF_SERIALIZATION { - p.Header.SerialID = constant.S_Proto - } - p.SetBody(impl.NewRequestPayload(request.args, request.atta)) - - if err := impl.LoadSerializer(p); err != nil { - return err - } - - if ct != impl.CT_OneWay { - p.Header.Type = impl.PackageRequest_TwoWay - rsp = NewPendingResponse() - rsp.response = response - rsp.callback = callback - } else { - p.Header.Type = impl.PackageRequest - } - if err = c.transfer(session, p, rsp); err != nil { - return perrors.WithStack(err) - } - - if ct == impl.CT_OneWay || callback != nil { - return nil - } - - select { - case <-getty.GetTimeWheel().After(c.Opts.RequestTimeout): - c.removePendingResponse(impl.SequenceType(rsp.seq)) - return perrors.WithStack(errClientReadTimeout) - case <-rsp.done: - err = rsp.err - } - - return perrors.WithStack(err) -} - -// Close close the client pool. -func (c *Client) Close() { - if c.Pool != nil { - c.Pool.close() - } - c.Pool = nil -} - -func (c *Client) selectSession(addr string) (*gettyRPCClient, getty.Session, error) { - rpcClient, err := c.Pool.getGettyRpcClient(impl.DUBBO, addr) - if err != nil { - return nil, nil, perrors.WithStack(err) - } - return rpcClient, rpcClient.selectSession(), nil -} - -func (c *Client) heartbeat(session getty.Session) error { - return c.transfer(session, nil, NewPendingResponse()) -} - -func (c *Client) transfer(session getty.Session, pkg *impl.DubboPackage, - rsp *PendingResponse) error { - - var ( - sequence uint64 - err error - ) - - sequence = c.Sequence.Add(1) - - if pkg == nil { - // make heartbeat package - header := impl.DubboHeader{ - Type: impl.PackageHeartbeat, - SerialID: constant.S_Hessian2, - } - pkg = NewClientRequestPackage(header, impl.Service{}) - // SetBody - reqPayload := impl.NewRequestPayload([]interface{}{}, nil) - pkg.SetBody(reqPayload) - // set serializer - if err := impl.LoadSerializer(pkg); err != nil { - return err - } - } - pkg.SetID(int64(sequence)) - - // cond1 - if rsp != nil { - rsp.seq = sequence - c.addPendingResponse(rsp) - } - - err = session.WritePkg(pkg, c.Opts.RequestTimeout) - if err != nil { - c.removePendingResponse(impl.SequenceType(rsp.seq)) - } else if rsp != nil { // cond2 - // cond2 should not merged with cond1. cause the response package may be returned very - // soon and it will be handled by other goroutine. - rsp.readStart = time.Now() - } - - return perrors.WithStack(err) -} - -func (c *Client) addPendingResponse(pr *PendingResponse) { - c.PendingResponses.Store(impl.SequenceType(pr.seq), pr) -} - -func (c *Client) removePendingResponse(seq impl.SequenceType) *PendingResponse { - if c.PendingResponses == nil { - return nil - } - if presp, ok := c.PendingResponses.Load(seq); ok { - c.PendingResponses.Delete(seq) - return presp.(*PendingResponse) - } - return nil -} - -// PendingResponse ... -type PendingResponse struct { - seq uint64 - err error - start time.Time - readStart time.Time - callback common.AsyncCallback - response *Response - done chan struct{} -} - -// NewPendingResponse create a PendingResponses. -func NewPendingResponse() *PendingResponse { - return &PendingResponse{ - start: time.Now(), - response: &Response{}, - done: make(chan struct{}), - } -} - -// GetCallResponse get AsyncCallbackResponse. -func (r PendingResponse) GetCallResponse() common.CallbackResponse { - return AsyncCallbackResponse{ - Cause: r.err, - Start: r.start, - ReadStart: r.readStart, - Reply: r.response, - } -} - -// client side request package, just for serialization -func NewClientRequestPackage(header impl.DubboHeader, svc impl.Service) *impl.DubboPackage { - return &impl.DubboPackage{ - Header: header, - Service: svc, - Body: nil, - Err: nil, - Codec: impl.NewDubboCodec(nil), - } -} diff --git a/protocol/dubbo/server.go b/protocol/dubbo/server.go deleted file mode 100644 index 134a58c68697ea86f6803adfb63e593f909c6717..0000000000000000000000000000000000000000 --- a/protocol/dubbo/server.go +++ /dev/null @@ -1,125 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache License, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package dubbo - -import ( - "context" - "fmt" - "net/url" -) -import ( - "github.com/opentracing/opentracing-go" - "gopkg.in/yaml.v2" -) - -import ( - "github.com/apache/dubbo-go/common" - "github.com/apache/dubbo-go/common/constant" - "github.com/apache/dubbo-go/common/logger" - "github.com/apache/dubbo-go/config" - "github.com/apache/dubbo-go/protocol" - "github.com/apache/dubbo-go/protocol/dubbo/impl" - "github.com/apache/dubbo-go/protocol/dubbo/impl/remoting" - "github.com/apache/dubbo-go/protocol/invocation" - "github.com/dubbogo/getty" -) - -func init() { - - // load clientconfig from provider_config - // default use dubbo - providerConfig := config.GetProviderConfig() - if providerConfig.ApplicationConfig == nil { - return - } - protocolConf := providerConfig.ProtocolConf - defaultServerConfig := remoting.GetDefaultServerConfig() - if protocolConf == nil { - logger.Info("protocol_conf default use dubbo config") - } else { - dubboConf := protocolConf.(map[interface{}]interface{})[DUBBO] - if dubboConf == nil { - logger.Warnf("dubboConf is nil") - return - } - - dubboConfByte, err := yaml.Marshal(dubboConf) - if err != nil { - panic(err) - } - err = yaml.Unmarshal(dubboConfByte, &defaultServerConfig) - if err != nil { - panic(err) - } - } - remoting.SetServerConfig(defaultServerConfig) -} - -// rebuildCtx rebuild the context by attachment. -// Once we decided to transfer more context's key-value, we should change this. -// now we only support rebuild the tracing context -func rebuildCtx(inv *invocation.RPCInvocation) context.Context { - ctx := context.Background() - - // actually, if user do not use any opentracing framework, the err will not be nil. - spanCtx, err := opentracing.GlobalTracer().Extract(opentracing.TextMap, - opentracing.TextMapCarrier(inv.Attachments())) - if err == nil { - ctx = context.WithValue(ctx, constant.TRACING_REMOTE_SPAN_CTX, spanCtx) - } - return ctx -} - -func NewStubHandler() remoting.StubHandler { - return remoting.StubFunc(func(session getty.Session, p *impl.DubboPackage) { - u := common.NewURLWithOptions(common.WithPath(p.GetService().Path), common.WithParams(url.Values{}), - common.WithParamsValue(constant.GROUP_KEY, p.GetService().Group), - common.WithParamsValue(constant.INTERFACE_KEY, p.GetService().Interface), - common.WithParamsValue(constant.VERSION_KEY, p.GetService().Version)) - - exporter, _ := dubboProtocol.ExporterMap().Load(u.ServiceKey()) - if exporter == nil { - err := fmt.Errorf("don't have this exporter, key: %s", u.ServiceKey()) - logger.Errorf(err.Error()) - p.SetResponseStatus(impl.Response_OK) - p.SetBody(err) - return - } - invoker := exporter.(protocol.Exporter).GetInvoker() - if invoker != nil { - attachments := p.GetBody().(map[string]interface{})["attachments"].(map[string]string) - attachments[constant.LOCAL_ADDR] = session.LocalAddr() - attachments[constant.REMOTE_ADDR] = session.RemoteAddr() - - args := p.GetBody().(map[string]interface{})["args"].([]interface{}) - inv := invocation.NewRPCInvocation(p.GetService().Method, args, attachments) - - ctx := rebuildCtx(inv) - result := invoker.Invoke(ctx, inv) - logger.Debugf("invoker result: %+v", result) - if err := result.Error(); err != nil { - p.SetResponseStatus(impl.Response_OK) - p.SetBody(&impl.ResponsePayload{nil, err, result.Attachments()}) - } else { - res := result.Result() - p.SetResponseStatus(impl.Response_OK) - p.SetBody(&impl.ResponsePayload{res, nil, result.Attachments()}) - } - } - }) -} diff --git a/protocol/invocation/rpcinvocation.go b/protocol/invocation/rpcinvocation.go index b8b5b509702ea5ee62df83eb55bf7f1c86625b26..a84b4a7975a57065f565885b33b5e7660981dfb4 100644 --- a/protocol/invocation/rpcinvocation.go +++ b/protocol/invocation/rpcinvocation.go @@ -18,11 +18,13 @@ package invocation import ( + "github.com/apache/dubbo-go/common" "reflect" "sync" ) import ( + "github.com/apache/dubbo-go/common/constant" "github.com/apache/dubbo-go/protocol" ) @@ -170,6 +172,11 @@ func (r *RPCInvocation) SetCallBack(c interface{}) { r.callBack = c } +func (r *RPCInvocation) ServiceKey() string { + return common.ServiceKey(r.AttachmentsByKey(constant.INTERFACE_KEY, ""), + r.AttachmentsByKey(constant.GROUP_KEY, ""), r.AttachmentsByKey(constant.VERSION_KEY, "")) +} + // ///////////////////////// // option // ///////////////////////// diff --git a/protocol/jsonrpc/http_test.go b/protocol/jsonrpc/http_test.go index f8480bf32e15ad428209eedb72757a299455eb20..188b036dfca5087f4729afbc64017d27b9aee902 100644 --- a/protocol/jsonrpc/http_test.go +++ b/protocol/jsonrpc/http_test.go @@ -22,19 +22,15 @@ import ( "strings" "testing" "time" -) -import ( "github.com/opentracing/opentracing-go" - perrors "github.com/pkg/errors" - "github.com/stretchr/testify/assert" -) -import ( "github.com/apache/dubbo-go/common" "github.com/apache/dubbo-go/common/constant" "github.com/apache/dubbo-go/common/proxy/proxy_factory" "github.com/apache/dubbo-go/protocol" + perrors "github.com/pkg/errors" + "github.com/stretchr/testify/assert" ) type ( @@ -71,7 +67,7 @@ func TestHTTPClient_Call(t *testing.T) { // call GetUser ctx := context.WithValue(context.Background(), constant.DUBBOGO_CTX_KEY, map[string]string{ - "X-Proxy-Id": "dubbogo", + "X-Proxy-ID": "dubbogo", "X-Services": url.Path, "X-Method": "GetUser", }) @@ -85,7 +81,7 @@ func TestHTTPClient_Call(t *testing.T) { // call GetUser0 ctx = context.WithValue(context.Background(), constant.DUBBOGO_CTX_KEY, map[string]string{ - "X-Proxy-Id": "dubbogo", + "X-Proxy-ID": "dubbogo", "X-Services": url.Path, "X-Method": "GetUser0", }) @@ -98,7 +94,7 @@ func TestHTTPClient_Call(t *testing.T) { // call GetUser1 ctx = context.WithValue(context.Background(), constant.DUBBOGO_CTX_KEY, map[string]string{ - "X-Proxy-Id": "dubbogo", + "X-Proxy-ID": "dubbogo", "X-Services": url.Path, "X-Method": "GetUser1", }) @@ -110,7 +106,7 @@ func TestHTTPClient_Call(t *testing.T) { // call GetUser2 ctx = context.WithValue(context.Background(), constant.DUBBOGO_CTX_KEY, map[string]string{ - "X-Proxy-Id": "dubbogo", + "X-Proxy-ID": "dubbogo", "X-Services": url.Path, "X-Method": "GetUser2", }) @@ -122,7 +118,7 @@ func TestHTTPClient_Call(t *testing.T) { // call GetUser3 ctx = context.WithValue(context.Background(), constant.DUBBOGO_CTX_KEY, map[string]string{ - "X-Proxy-Id": "dubbogo", + "X-Proxy-ID": "dubbogo", "X-Services": url.Path, "X-Method": "GetUser3", }) @@ -134,7 +130,7 @@ func TestHTTPClient_Call(t *testing.T) { // call GetUser4 ctx = context.WithValue(context.Background(), constant.DUBBOGO_CTX_KEY, map[string]string{ - "X-Proxy-Id": "dubbogo", + "X-Proxy-ID": "dubbogo", "X-Services": url.Path, "X-Method": "GetUser4", }) @@ -145,7 +141,7 @@ func TestHTTPClient_Call(t *testing.T) { assert.Equal(t, &User{Id: "", Name: ""}, reply) ctx = context.WithValue(context.Background(), constant.DUBBOGO_CTX_KEY, map[string]string{ - "X-Proxy-Id": "dubbogo", + "X-Proxy-ID": "dubbogo", "X-Services": url.Path, "X-Method": "GetUser4", }) diff --git a/protocol/jsonrpc/jsonrpc_invoker.go b/protocol/jsonrpc/jsonrpc_invoker.go index d84b980216ade6e569e68af31fc90e1ea16b3056..6b3df4a52728060f79ecf25de7e0f81ed5c55854 100644 --- a/protocol/jsonrpc/jsonrpc_invoker.go +++ b/protocol/jsonrpc/jsonrpc_invoker.go @@ -19,13 +19,12 @@ package jsonrpc import ( "context" -) -import ( "github.com/apache/dubbo-go/common" "github.com/apache/dubbo-go/common/constant" "github.com/apache/dubbo-go/common/logger" "github.com/apache/dubbo-go/protocol" + invocation_impl "github.com/apache/dubbo-go/protocol/invocation" ) diff --git a/protocol/protocolwrapper/protocol_filter_wrapper.go b/protocol/protocolwrapper/protocol_filter_wrapper.go index cba1d5d5bce8c3de387381d17cc3f7965bf3adac..6cb343c160323805836aa00a78def97bf5878b54 100644 --- a/protocol/protocolwrapper/protocol_filter_wrapper.go +++ b/protocol/protocolwrapper/protocol_filter_wrapper.go @@ -68,6 +68,9 @@ func (pfw *ProtocolFilterWrapper) Destroy() { } func buildInvokerChain(invoker protocol.Invoker, key string) protocol.Invoker { + if invoker == nil { + return nil + } filtName := invoker.GetUrl().GetParam(key, "") if filtName == "" { return invoker diff --git a/protocol/dubbo/impl/remoting/errors.go b/remoting/codec.go similarity index 61% rename from protocol/dubbo/impl/remoting/errors.go rename to remoting/codec.go index 8fda9b31ff07c050b0658c2105b0c6d002a88b5c..607d1643cc1967e93bf5288d8d4c0788c73a735e 100644 --- a/protocol/dubbo/impl/remoting/errors.go +++ b/remoting/codec.go @@ -15,3 +15,31 @@ * limitations under the License. */ package remoting + +import ( + "bytes" +) + +// codec for exchangeClient +type Codec interface { + EncodeRequest(request *Request) (*bytes.Buffer, error) + EncodeResponse(response *Response) (*bytes.Buffer, error) + Decode(data []byte) (DecodeResult, int, error) +} + +type DecodeResult struct { + IsRequest bool + Result interface{} +} + +var ( + codec = make(map[string]Codec, 2) +) + +func RegistryCodec(protocol string, codecTmp Codec) { + codec[protocol] = codecTmp +} + +func GetCodec(protocol string) Codec { + return codec[protocol] +} diff --git a/remoting/exchange.go b/remoting/exchange.go new file mode 100644 index 0000000000000000000000000000000000000000..b97d6073c1f734d91c094372cf7de0919d088d99 --- /dev/null +++ b/remoting/exchange.go @@ -0,0 +1,144 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package remoting + +import ( + "time" +) + +import ( + "go.uber.org/atomic" +) + +import ( + "github.com/apache/dubbo-go/common" +) + +var ( + // generate request ID for global use + sequence atomic.Int64 +) + +func init() { + // init request ID + sequence.Store(0) +} + +func SequenceId() int64 { + // increse 2 for every request as the same before. + // We expect that the request from client to server, the requestId is even; but from server to client, the requestId is odd. + return sequence.Add(2) +} + +// this is request for transport layer +type Request struct { + ID int64 + // protocol version + Version string + // serial ID (ignore) + SerialID byte + // Data + Data interface{} + TwoWay bool + Event bool +} + +// NewRequest aims to create Request. +// The ID is auto increase. +func NewRequest(version string) *Request { + return &Request{ + ID: SequenceId(), + Version: version, + } +} + +// this is response for transport layer +type Response struct { + ID int64 + Version string + SerialID byte + Status uint8 + Event bool + Error error + Result interface{} +} + +// NewResponse aims to create Response +func NewResponse(id int64, version string) *Response { + return &Response{ + ID: id, + Version: version, + } +} + +// the response is heartbeat +func (response *Response) IsHeartbeat() bool { + return response.Event && response.Result == nil +} + +type Options struct { + // connect timeout + ConnectTimeout time.Duration +} + +//AsyncCallbackResponse async response for dubbo +type AsyncCallbackResponse struct { + common.CallbackResponse + Opts Options + Cause error + Start time.Time // invoke(call) start time == write start time + ReadStart time.Time // read start time, write duration = ReadStart - Start + Reply interface{} +} + +// the client sends requst to server, there is one pendingResponse at client side to wait the response from server +type PendingResponse struct { + seq int64 + Err error + start time.Time + ReadStart time.Time + Callback common.AsyncCallback + response *Response + Reply interface{} + Done chan struct{} +} + +// NewPendingResponse aims to create PendingResponse. +// Id is always from ID of Request +func NewPendingResponse(id int64) *PendingResponse { + return &PendingResponse{ + seq: id, + start: time.Now(), + response: &Response{}, + Done: make(chan struct{}), + } +} + +func (r *PendingResponse) SetResponse(response *Response) { + r.response = response +} + +// GetCallResponse is used for callback of async. +// It is will return AsyncCallbackResponse. +func (r PendingResponse) GetCallResponse() common.CallbackResponse { + return AsyncCallbackResponse{ + Cause: r.Err, + Start: r.start, + ReadStart: r.ReadStart, + Reply: r.response, + } +} diff --git a/remoting/exchange_client.go b/remoting/exchange_client.go new file mode 100644 index 0000000000000000000000000000000000000000..7c0520ab9cb599ae1a4d09c575c9f4389aa007eb --- /dev/null +++ b/remoting/exchange_client.go @@ -0,0 +1,227 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package remoting + +import ( + "errors" + "sync" + "time" +) + +import ( + "github.com/apache/dubbo-go/common" + "github.com/apache/dubbo-go/common/logger" + "github.com/apache/dubbo-go/protocol" +) + +var ( + // store requestID and response + pendingResponses *sync.Map = new(sync.Map) +) + +type SequenceType int64 + +// It is interface of client for network communication. +// If you use getty as network communication, you should define GettyClient that implements this interface. +type Client interface { + SetExchangeClient(client *ExchangeClient) + // responseHandler is used to deal with msg + SetResponseHandler(responseHandler ResponseHandler) + // connect url + Connect(url common.URL) error + // close + Close() + // send request to server. + Request(request *Request, timeout time.Duration, response *PendingResponse) error +} + +// This is abstraction level. it is like facade. +type ExchangeClient struct { + // connect server timeout + ConnectTimeout time.Duration + // to dial server address. The format: ip:port + address string + // the client that will deal with the transport. It is interface, and it will use gettyClient by default. + client Client + // the tag for init. + init bool +} + +// handle the message from server +type ResponseHandler interface { + Handler(response *Response) +} + +// create ExchangeClient +func NewExchangeClient(url common.URL, client Client, connectTimeout time.Duration, lazyInit bool) *ExchangeClient { + exchangeClient := &ExchangeClient{ + ConnectTimeout: connectTimeout, + address: url.Location, + client: client, + } + client.SetExchangeClient(exchangeClient) + if !lazyInit { + if err := exchangeClient.doInit(url); err != nil { + return nil + } + } + + client.SetResponseHandler(exchangeClient) + return exchangeClient +} + +func (cl *ExchangeClient) doInit(url common.URL) error { + if cl.init { + return nil + } + if cl.client.Connect(url) != nil { + //retry for a while + time.Sleep(100 * time.Millisecond) + if cl.client.Connect(url) != nil { + logger.Errorf("Failed to connect server %+v " + url.Location) + return errors.New("Failed to connect server " + url.Location) + } + } + //FIXME atomic operation + cl.init = true + return nil +} + +// two way request +func (client *ExchangeClient) Request(invocation *protocol.Invocation, url common.URL, timeout time.Duration, + result *protocol.RPCResult) error { + if er := client.doInit(url); er != nil { + return er + } + request := NewRequest("2.0.2") + request.Data = invocation + request.Event = false + request.TwoWay = true + + rsp := NewPendingResponse(request.ID) + rsp.response = NewResponse(request.ID, "2.0.2") + rsp.Reply = (*invocation).Reply() + AddPendingResponse(rsp) + + err := client.client.Request(request, timeout, rsp) + // request error + if err != nil { + result.Err = err + return err + } + if resultTmp, ok := rsp.response.Result.(*protocol.RPCResult); ok { + result.Rest = resultTmp.Rest + result.Attrs = resultTmp.Attrs + result.Err = resultTmp.Err + } + return nil +} + +// async two way request +func (client *ExchangeClient) AsyncRequest(invocation *protocol.Invocation, url common.URL, timeout time.Duration, + callback common.AsyncCallback, result *protocol.RPCResult) error { + if er := client.doInit(url); er != nil { + return er + } + request := NewRequest("2.0.2") + request.Data = invocation + request.Event = false + request.TwoWay = true + + rsp := NewPendingResponse(request.ID) + rsp.response = NewResponse(request.ID, "2.0.2") + rsp.Callback = callback + rsp.Reply = (*invocation).Reply() + AddPendingResponse(rsp) + + err := client.client.Request(request, timeout, rsp) + if err != nil { + result.Err = err + return err + } + result.Rest = rsp.response + return nil +} + +// oneway request +func (client *ExchangeClient) Send(invocation *protocol.Invocation, url common.URL, timeout time.Duration) error { + if er := client.doInit(url); er != nil { + return er + } + request := NewRequest("2.0.2") + request.Data = invocation + request.Event = false + request.TwoWay = false + + rsp := NewPendingResponse(request.ID) + rsp.response = NewResponse(request.ID, "2.0.2") + + err := client.client.Request(request, timeout, rsp) + if err != nil { + return err + } + return nil +} + +// close client +func (client *ExchangeClient) Close() { + client.client.Close() +} + +// handle the response from server +func (client *ExchangeClient) Handler(response *Response) { + + pendingResponse := removePendingResponse(SequenceType(response.ID)) + if pendingResponse == nil { + logger.Errorf("failed to get pending response context for response package %s", *response) + return + } + + pendingResponse.response = response + + if pendingResponse.Callback == nil { + pendingResponse.Err = pendingResponse.response.Error + pendingResponse.Done <- struct{}{} + } else { + pendingResponse.Callback(pendingResponse.GetCallResponse()) + } +} + +// store response into map +func AddPendingResponse(pr *PendingResponse) { + pendingResponses.Store(SequenceType(pr.seq), pr) +} + +// get and remove response +func removePendingResponse(seq SequenceType) *PendingResponse { + if pendingResponses == nil { + return nil + } + if presp, ok := pendingResponses.Load(seq); ok { + pendingResponses.Delete(seq) + return presp.(*PendingResponse) + } + return nil +} + +// get response +func GetPendingResponse(seq SequenceType) *PendingResponse { + if presp, ok := pendingResponses.Load(seq); ok { + return presp.(*PendingResponse) + } + return nil +} diff --git a/remoting/exchange_server.go b/remoting/exchange_server.go new file mode 100644 index 0000000000000000000000000000000000000000..c4538075b53b9451bcf7ccc8cb981926b6adeb43 --- /dev/null +++ b/remoting/exchange_server.go @@ -0,0 +1,53 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package remoting + +import ( + "github.com/apache/dubbo-go/common" +) + +// It is interface of server for network communication. +// If you use getty as network communication, you should define GettyServer that implements this interface. +type Server interface { + //invoke once for connection + Start() + //it is for destroy + Stop() +} + +// This is abstraction level. it is like facade. +type ExchangeServer struct { + Server Server +} + +// Create ExchangeServer +func NewExchangeServer(url common.URL, server Server) *ExchangeServer { + exchangServer := &ExchangeServer{ + Server: server, + } + return exchangServer +} + +// start server +func (server *ExchangeServer) Start() { + server.Server.Start() +} + +// stop server +func (server *ExchangeServer) Stop() { + server.Server.Stop() +} diff --git a/protocol/dubbo/impl/remoting/config.go b/remoting/getty/config.go similarity index 75% rename from protocol/dubbo/impl/remoting/config.go rename to remoting/getty/config.go index 1f26422f79055ab126a8ef89446c84ed05cbdbab..d63a45a2e4d4d2e2b53c13b2de50a76fc3230b65 100644 --- a/protocol/dubbo/impl/remoting/config.go +++ b/remoting/getty/config.go @@ -15,7 +15,7 @@ * limitations under the License. */ -package remoting +package getty import ( "time" @@ -27,32 +27,33 @@ import ( ) type ( - // GettySessionParam is session configuration for getty. + // GettySessionParam ... GettySessionParam struct { CompressEncoding bool `default:"false" yaml:"compress_encoding" json:"compress_encoding,omitempty"` TcpNoDelay bool `default:"true" yaml:"tcp_no_delay" json:"tcp_no_delay,omitempty"` TcpKeepAlive bool `default:"true" yaml:"tcp_keep_alive" json:"tcp_keep_alive,omitempty"` KeepAlivePeriod string `default:"180s" yaml:"keep_alive_period" json:"keep_alive_period,omitempty"` - KeepAlivePeriodD time.Duration + keepAlivePeriod time.Duration TcpRBufSize int `default:"262144" yaml:"tcp_r_buf_size" json:"tcp_r_buf_size,omitempty"` TcpWBufSize int `default:"65536" yaml:"tcp_w_buf_size" json:"tcp_w_buf_size,omitempty"` PkgWQSize int `default:"1024" yaml:"pkg_wq_size" json:"pkg_wq_size,omitempty"` TcpReadTimeout string `default:"1s" yaml:"tcp_read_timeout" json:"tcp_read_timeout,omitempty"` - TcpReadTimeoutD time.Duration + tcpReadTimeout time.Duration TcpWriteTimeout string `default:"5s" yaml:"tcp_write_timeout" json:"tcp_write_timeout,omitempty"` - TcpWriteTimeoutD time.Duration + tcpWriteTimeout time.Duration WaitTimeout string `default:"7s" yaml:"wait_timeout" json:"wait_timeout,omitempty"` - WaitTimeoutD time.Duration + waitTimeout time.Duration MaxMsgLen int `default:"1024" yaml:"max_msg_len" json:"max_msg_len,omitempty"` SessionName string `default:"rpc" yaml:"session_name" json:"session_name,omitempty"` } - // ServerConfig holds supported types by the multiconfig package + // ServerConfig + //Config holds supported types by the multiconfig package ServerConfig struct { // session - SessionTimeout string `default:"60s" yaml:"session_timeout" json:"session_timeout,omitempty"` - SessionTimeoutD time.Duration - SessionNumber int `default:"1000" yaml:"session_number" json:"session_number,omitempty"` + SessionTimeout string `default:"60s" yaml:"session_timeout" json:"session_timeout,omitempty"` + sessionTimeout time.Duration + SessionNumber int `default:"1000" yaml:"session_number" json:"session_number,omitempty"` // grpool GrPoolSize int `default:"0" yaml:"gr_pool_size" json:"gr_pool_size,omitempty"` @@ -63,20 +64,21 @@ type ( GettySessionParam GettySessionParam `required:"true" yaml:"getty_session_param" json:"getty_session_param,omitempty"` } - // ClientConfig holds supported types by the multiconfig package + // ClientConfig + //Config holds supported types by the multiconfig package ClientConfig struct { ReconnectInterval int `default:"0" yaml:"reconnect_interval" json:"reconnect_interval,omitempty"` - // session Pool + // session pool ConnectionNum int `default:"16" yaml:"connection_number" json:"connection_number,omitempty"` // heartbeat - HeartbeatPeriod string `default:"15s" yaml:"heartbeat_period" json:"heartbeat_period,omitempty"` - HeartbeatPeriodD time.Duration + HeartbeatPeriod string `default:"15s" yaml:"heartbeat_period" json:"heartbeat_period,omitempty"` + heartbeatPeriod time.Duration // session - SessionTimeout string `default:"60s" yaml:"session_timeout" json:"session_timeout,omitempty"` - SessionTimeoutD time.Duration + SessionTimeout string `default:"60s" yaml:"session_timeout" json:"session_timeout,omitempty"` + sessionTimeout time.Duration // Connection Pool PoolSize int `default:"2" yaml:"pool_size" json:"pool_size,omitempty"` @@ -89,13 +91,10 @@ type ( // session tcp parameters GettySessionParam GettySessionParam `required:"true" yaml:"getty_session_param" json:"getty_session_param,omitempty"` - - // serialization - Serialization string `default:"hessian2" yaml:"serialization" json:"serialization"` } ) -// GetDefaultClientConfig gets client default configuration. +// GetDefaultClientConfig ... func GetDefaultClientConfig() ClientConfig { return ClientConfig{ ReconnectInterval: 0, @@ -107,7 +106,6 @@ func GetDefaultClientConfig() ClientConfig { GrPoolSize: 200, QueueLen: 64, QueueNumber: 10, - Serialization: "hessian2", GettySessionParam: GettySessionParam{ CompressEncoding: false, TcpNoDelay: true, @@ -124,7 +122,7 @@ func GetDefaultClientConfig() ClientConfig { }} } -// GetDefaultServerConfig gets server default configuration. +// GetDefaultServerConfig ... func GetDefaultServerConfig() ServerConfig { return ServerConfig{ SessionTimeout: "180s", @@ -149,60 +147,60 @@ func GetDefaultServerConfig() ServerConfig { } } -// CheckValidity confirm getty sessian params. +// CheckValidity ... func (c *GettySessionParam) CheckValidity() error { var err error - if c.KeepAlivePeriodD, err = time.ParseDuration(c.KeepAlivePeriod); err != nil { + if c.keepAlivePeriod, err = time.ParseDuration(c.KeepAlivePeriod); err != nil { return perrors.WithMessagef(err, "time.ParseDuration(KeepAlivePeriod{%#v})", c.KeepAlivePeriod) } - if c.TcpReadTimeoutD, err = time.ParseDuration(c.TcpReadTimeout); err != nil { + if c.tcpReadTimeout, err = time.ParseDuration(c.TcpReadTimeout); err != nil { return perrors.WithMessagef(err, "time.ParseDuration(TcpReadTimeout{%#v})", c.TcpReadTimeout) } - if c.TcpWriteTimeoutD, err = time.ParseDuration(c.TcpWriteTimeout); err != nil { + if c.tcpWriteTimeout, err = time.ParseDuration(c.TcpWriteTimeout); err != nil { return perrors.WithMessagef(err, "time.ParseDuration(TcpWriteTimeout{%#v})", c.TcpWriteTimeout) } - if c.WaitTimeoutD, err = time.ParseDuration(c.WaitTimeout); err != nil { + if c.waitTimeout, err = time.ParseDuration(c.WaitTimeout); err != nil { return perrors.WithMessagef(err, "time.ParseDuration(WaitTimeout{%#v})", c.WaitTimeout) } return nil } -// CheckValidity confirm client params. +// CheckValidity ... func (c *ClientConfig) CheckValidity() error { var err error c.ReconnectInterval = c.ReconnectInterval * 1e6 - if c.HeartbeatPeriodD, err = time.ParseDuration(c.HeartbeatPeriod); err != nil { + if c.heartbeatPeriod, err = time.ParseDuration(c.HeartbeatPeriod); err != nil { return perrors.WithMessagef(err, "time.ParseDuration(HeartbeatPeroid{%#v})", c.HeartbeatPeriod) } - if c.HeartbeatPeriodD >= time.Duration(getty.MaxWheelTimeSpan) { + if c.heartbeatPeriod >= time.Duration(getty.MaxWheelTimeSpan) { return perrors.WithMessagef(err, "heartbeat_period %s should be less than %s", c.HeartbeatPeriod, time.Duration(getty.MaxWheelTimeSpan)) } - if c.SessionTimeoutD, err = time.ParseDuration(c.SessionTimeout); err != nil { + if c.sessionTimeout, err = time.ParseDuration(c.SessionTimeout); err != nil { return perrors.WithMessagef(err, "time.ParseDuration(SessionTimeout{%#v})", c.SessionTimeout) } return perrors.WithStack(c.GettySessionParam.CheckValidity()) } -// CheckValidity confirm server params. +// CheckValidity ... func (c *ServerConfig) CheckValidity() error { var err error - if c.SessionTimeoutD, err = time.ParseDuration(c.SessionTimeout); err != nil { + if c.sessionTimeout, err = time.ParseDuration(c.SessionTimeout); err != nil { return perrors.WithMessagef(err, "time.ParseDuration(SessionTimeout{%#v})", c.SessionTimeout) } - if c.SessionTimeoutD >= time.Duration(getty.MaxWheelTimeSpan) { + if c.sessionTimeout >= time.Duration(getty.MaxWheelTimeSpan) { return perrors.WithMessagef(err, "session_timeout %s should be less than %s", c.SessionTimeout, time.Duration(getty.MaxWheelTimeSpan)) } diff --git a/remoting/getty/dubbo_codec_for_test.go b/remoting/getty/dubbo_codec_for_test.go new file mode 100644 index 0000000000000000000000000000000000000000..f6460cde9ddb420eb57758bd32150bb84fea9dd0 --- /dev/null +++ b/remoting/getty/dubbo_codec_for_test.go @@ -0,0 +1,382 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package getty + +// copy from dubbo/dubbo_codec.go . +// it is used to unit test. +import ( + "bufio" + "bytes" + "fmt" + "strconv" + "time" +) +import ( + hessian "github.com/apache/dubbo-go-hessian2" + perrors "github.com/pkg/errors" +) + +import ( + "github.com/apache/dubbo-go/common/constant" + "github.com/apache/dubbo-go/common/logger" + "github.com/apache/dubbo-go/protocol" + "github.com/apache/dubbo-go/protocol/invocation" + "github.com/apache/dubbo-go/remoting" +) + +//SerialID serial ID +type SerialID byte + +const ( + // S_Dubbo dubbo serial id + S_Dubbo SerialID = 2 +) + +func init() { + codec := &DubboTestCodec{} + remoting.RegistryCodec("dubbo", codec) +} + +// DubboPackage ... +type DubboPackage struct { + Header hessian.DubboHeader + Service hessian.Service + Body interface{} + Err error +} + +func (p DubboPackage) String() string { + return fmt.Sprintf("DubboPackage: Header-%v, Path-%v, Body-%v", p.Header, p.Service, p.Body) +} + +// Marshal ... +func (p *DubboPackage) Marshal() (*bytes.Buffer, error) { + codec := hessian.NewHessianCodec(nil) + + pkg, err := codec.Write(p.Service, p.Header, p.Body) + if err != nil { + return nil, perrors.WithStack(err) + } + + return bytes.NewBuffer(pkg), nil +} + +// Unmarshal ... +func (p *DubboPackage) Unmarshal(buf *bytes.Buffer, resp *remoting.Response) error { + // fix issue https://github.com/apache/dubbo-go/issues/380 + bufLen := buf.Len() + if bufLen < hessian.HEADER_LENGTH { + return perrors.WithStack(hessian.ErrHeaderNotEnough) + } + + codec := hessian.NewHessianCodec(bufio.NewReaderSize(buf, bufLen)) + + // read header + err := codec.ReadHeader(&p.Header) + if err != nil { + return perrors.WithStack(err) + } + + if resp != nil { // for client + if (p.Header.Type & hessian.PackageRequest) != 0x00 { + // size of this array must be '7' + // https://github.com/apache/dubbo-go-hessian2/blob/master/request.go#L272 + p.Body = make([]interface{}, 7) + } else { + pendingRsp := remoting.GetPendingResponse(remoting.SequenceType(p.Header.ID)) + if pendingRsp == nil { + return perrors.Errorf("client.GetPendingResponse(%v) = nil", p.Header.ID) + } + p.Body = &hessian.Response{RspObj: pendingRsp.Reply} + } + } + + // read body + err = codec.ReadBody(p.Body) + return perrors.WithStack(err) +} + +type DubboTestCodec struct { +} + +func (c *DubboTestCodec) EncodeRequest(request *remoting.Request) (*bytes.Buffer, error) { + if request.Event { + return c.encodeHeartbeartReqeust(request) + } + + invoc, ok := request.Data.(*invocation.RPCInvocation) + if !ok { + logger.Errorf("encode request failed for parameter type :%+v", request) + return nil, perrors.Errorf("encode request failed for parameter type :%+v", request) + } + invocation := *invoc + + p := &DubboPackage{} + p.Service.Path = invocation.AttachmentsByKey(constant.PATH_KEY, "") + p.Service.Interface = invocation.AttachmentsByKey(constant.INTERFACE_KEY, "") + p.Service.Version = invocation.AttachmentsByKey(constant.VERSION_KEY, "") + p.Service.Group = invocation.AttachmentsByKey(constant.GROUP_KEY, "") + p.Service.Method = invocation.MethodName() + + timeout, err := strconv.Atoi(invocation.AttachmentsByKey(constant.TIMEOUT_KEY, "3000")) + if err != nil { + // it will be wrapped in readwrite.Write . + return nil, err + } + p.Service.Timeout = time.Duration(timeout) + //var timeout = request.svcUrl.GetParam(strings.Join([]string{constant.METHOD_KEYS, request.method + constant.RETRIES_KEY}, "."), "") + //if len(timeout) != 0 { + // if t, err := time.ParseDuration(timeout); err == nil { + // p.Service.Timeout = t + // } + //} + + p.Header.SerialID = byte(S_Dubbo) + p.Header.ID = request.ID + if request.TwoWay { + p.Header.Type = hessian.PackageRequest_TwoWay + } else { + p.Header.Type = hessian.PackageRequest + } + + p.Body = hessian.NewRequest(invocation.Arguments(), invocation.Attachments()) + + codec := hessian.NewHessianCodec(nil) + + pkg, err := codec.Write(p.Service, p.Header, p.Body) + if err != nil { + return nil, perrors.WithStack(err) + } + + return bytes.NewBuffer(pkg), nil +} +func (c *DubboTestCodec) encodeHeartbeartReqeust(request *remoting.Request) (*bytes.Buffer, error) { + pkg := &DubboPackage{} + pkg.Body = []interface{}{} + pkg.Header.ID = request.ID + pkg.Header.Type = hessian.PackageHeartbeat + pkg.Header.SerialID = byte(S_Dubbo) + + codec := hessian.NewHessianCodec(nil) + + byt, err := codec.Write(pkg.Service, pkg.Header, pkg.Body) + if err != nil { + return nil, perrors.WithStack(err) + } + + return bytes.NewBuffer(byt), nil +} +func (c *DubboTestCodec) EncodeResponse(response *remoting.Response) (*bytes.Buffer, error) { + var ptype = hessian.PackageResponse + if response.IsHeartbeat() { + ptype = hessian.PackageHeartbeat + } + resp := &DubboPackage{ + Header: hessian.DubboHeader{ + SerialID: response.SerialID, + Type: ptype, + ID: response.ID, + ResponseStatus: response.Status, + }, + } + if !response.IsHeartbeat() { + resp.Body = &hessian.Response{ + RspObj: response.Result.(protocol.RPCResult).Rest, + Exception: response.Result.(protocol.RPCResult).Err, + Attachments: response.Result.(protocol.RPCResult).Attrs, + } + } + + //if response.Header.Type&hessian.PackageRequest != 0x00 { + // resp.Body = req.Body + //} else { + // resp.Body = nil + //} + codec := hessian.NewHessianCodec(nil) + + pkg, err := codec.Write(resp.Service, resp.Header, resp.Body) + if err != nil { + return nil, perrors.WithStack(err) + } + + return bytes.NewBuffer(pkg), nil +} +func (c *DubboTestCodec) Decode(data []byte) (remoting.DecodeResult, int, error) { + if c.isRequest(data) { + req, len, err := c.decodeRequest(data) + if err != nil { + return remoting.DecodeResult{}, len, err + } + return remoting.DecodeResult{IsRequest: true, Result: req}, len, err + } else { + resp, len, err := c.decodeResponse(data) + if err != nil { + return remoting.DecodeResult{}, len, err + } + return remoting.DecodeResult{IsRequest: false, Result: resp}, len, err + } +} +func (c *DubboTestCodec) isRequest(data []byte) bool { + if data[2]&byte(0x80) == 0x00 { + return false + } + return true +} + +func (c *DubboTestCodec) decodeRequest(data []byte) (*remoting.Request, int, error) { + pkg := &DubboPackage{ + Body: make([]interface{}, 7), + } + var request *remoting.Request = nil + buf := bytes.NewBuffer(data) + err := pkg.Unmarshal(buf, nil) + if err != nil { + originErr := perrors.Cause(err) + if originErr == hessian.ErrHeaderNotEnough || originErr == hessian.ErrBodyNotEnough { + //FIXME + return nil, 0, originErr + } + logger.Errorf("pkg.Unmarshal(len(@data):%d) = error:%+v", buf.Len(), err) + + return request, 0, perrors.WithStack(err) + } + request = &remoting.Request{ + ID: pkg.Header.ID, + SerialID: pkg.Header.SerialID, + TwoWay: pkg.Header.Type&hessian.PackageRequest_TwoWay != 0x00, + Event: pkg.Header.Type&hessian.PackageHeartbeat != 0x00, + } + if pkg.Header.Type&hessian.PackageHeartbeat == 0x00 { + // convert params of request + req := pkg.Body.([]interface{}) // length of body should be 7 + if len(req) > 0 { + //invocation := request.Data.(*invocation.RPCInvocation) + var methodName string + var args []interface{} + var attachments map[string]string = make(map[string]string) + if req[0] != nil { + //dubbo version + request.Version = req[0].(string) + } + if req[1] != nil { + //path + attachments[constant.PATH_KEY] = req[1].(string) + } + if req[2] != nil { + //version + attachments[constant.VERSION_KEY] = req[2].(string) + } + if req[3] != nil { + //method + methodName = req[3].(string) + } + if req[4] != nil { + //argsType + //invocation.ParameterTypes(constant., req[1].(string)) + //argsTypes = req[4].(string) + } + if req[5] != nil { + args = req[5].([]interface{}) + } + if req[6] != nil { + attachments = req[6].(map[string]string) + } + //if pkg.Service.Path == "" && len(attachments[constant.PATH_KEY]) > 0 { + // pkg.Service.Path = attachments[constant.PATH_KEY] + //} + //if _, ok := attachments[constant.INTERFACE_KEY]; ok { + // pkg.Service.Interface = attachments[constant.INTERFACE_KEY] + //} else { + // pkg.Service.Interface = pkg.Service.Path + //} + //if len(attachments[constant.GROUP_KEY]) > 0 { + // pkg.Service.Group = attachments[constant.GROUP_KEY] + //} + invoc := invocation.NewRPCInvocationWithOptions(invocation.WithAttachments(attachments), + invocation.WithArguments(args), invocation.WithMethodName(methodName)) + request.Data = invoc + //pkg.Body = map[string]interface{}{ + // "dubboVersion": dubboVersion, + // "argsTypes": argsTypes, + // "args": args, + // "service": common.ServiceMap.GetService("dubbo", pkg.Service.Path), // path as a key + // "attachments": attachments, + //} + } + } + return request, hessian.HEADER_LENGTH + pkg.Header.BodyLen, nil +} + +func (c *DubboTestCodec) decodeResponse(data []byte) (*remoting.Response, int, error) { + pkg := &DubboPackage{} + buf := bytes.NewBuffer(data) + response := &remoting.Response{} + err := pkg.Unmarshal(buf, response) + if err != nil { + originErr := perrors.Cause(err) + if originErr == hessian.ErrHeaderNotEnough || originErr == hessian.ErrBodyNotEnough { + return nil, 0, originErr + } + logger.Errorf("pkg.Unmarshal(len(@data):%d) = error:%+v", buf.Len(), err) + + return response, 0, perrors.WithStack(err) + } + response = &remoting.Response{ + ID: pkg.Header.ID, + //Version: pkg.Header., + SerialID: pkg.Header.SerialID, + Status: pkg.Header.ResponseStatus, + Event: (pkg.Header.Type & hessian.PackageHeartbeat) != 0, + } + var error error + if pkg.Header.Type&hessian.PackageHeartbeat != 0x00 { + if pkg.Header.Type&hessian.PackageResponse != 0x00 { + logger.Debugf("get rpc heartbeat response{header: %#v, body: %#v}", pkg.Header, pkg.Body) + if pkg.Err != nil { + logger.Errorf("rpc heartbeat response{error: %#v}", pkg.Err) + error = pkg.Err + } + } else { + logger.Debugf("get rpc heartbeat request{header: %#v, service: %#v, body: %#v}", pkg.Header, pkg.Service, pkg.Body) + response.Status = hessian.Response_OK + //reply(session, p, hessian.PackageHeartbeat) + } + return response, hessian.HEADER_LENGTH + pkg.Header.BodyLen, error + } + logger.Debugf("get rpc response{header: %#v, body: %#v}", pkg.Header, pkg.Body) + rpcResult := &protocol.RPCResult{} + response.Result = rpcResult + if pkg.Header.Type&hessian.PackageRequest == 0x00 { + if pkg.Err != nil { + rpcResult.Err = pkg.Err + } else if pkg.Body.(*hessian.Response).Exception != nil { + rpcResult.Err = pkg.Body.(*hessian.Response).Exception + response.Error = rpcResult.Err + } + rpcResult.Attrs = pkg.Body.(*hessian.Response).Attachments + rpcResult.Rest = pkg.Body.(*hessian.Response).RspObj + } + + //h.conn.updateSession(session) + //pendingResponse := h.conn.pool.rpcClient.removePendingResponse(SequenceType(p.Header.ID)) + //if pendingResponse == nil { + // logger.Errorf("failed to get pending response context for response package %s", *p) + // return + //} + + return response, hessian.HEADER_LENGTH + pkg.Header.BodyLen, nil +} diff --git a/remoting/getty/getty_client.go b/remoting/getty/getty_client.go new file mode 100644 index 0000000000000000000000000000000000000000..7055b985056a5c2d7d7aeb325ac3f367953347ea --- /dev/null +++ b/remoting/getty/getty_client.go @@ -0,0 +1,219 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package getty + +import ( + "math/rand" + "time" +) + +import ( + "github.com/dubbogo/getty" + gxsync "github.com/dubbogo/gost/sync" + perrors "github.com/pkg/errors" + "gopkg.in/yaml.v2" +) + +import ( + "github.com/apache/dubbo-go/common" + "github.com/apache/dubbo-go/common/logger" + "github.com/apache/dubbo-go/config" + "github.com/apache/dubbo-go/remoting" +) + +var ( + errInvalidCodecType = perrors.New("illegal CodecType") + errInvalidAddress = perrors.New("remote address invalid or empty") + errSessionNotExist = perrors.New("session not exist") + errClientClosed = perrors.New("client closed") + errClientReadTimeout = perrors.New("client read timeout") + + clientConf *ClientConfig + clientGrpool *gxsync.TaskPool +) + +// it is init client for single protocol. +func initClient(protocol string) { + if protocol == "" { + return + } + + // load clientconfig from consumer_config + // default use dubbo + consumerConfig := config.GetConsumerConfig() + if consumerConfig.ApplicationConfig == nil { + return + } + protocolConf := config.GetConsumerConfig().ProtocolConf + defaultClientConfig := GetDefaultClientConfig() + if protocolConf == nil { + logger.Info("protocol_conf default use dubbo config") + } else { + dubboConf := protocolConf.(map[interface{}]interface{})[protocol] + if dubboConf == nil { + logger.Warnf("dubboConf is nil") + return + } + dubboConfByte, err := yaml.Marshal(dubboConf) + if err != nil { + panic(err) + } + err = yaml.Unmarshal(dubboConfByte, &defaultClientConfig) + if err != nil { + panic(err) + } + } + clientConf = &defaultClientConfig + if err := clientConf.CheckValidity(); err != nil { + logger.Warnf("[CheckValidity] error: %v", err) + return + } + setClientGrpool() + + rand.Seed(time.Now().UnixNano()) +} + +// Config ClientConf +func SetClientConf(c ClientConfig) { + clientConf = &c + err := clientConf.CheckValidity() + if err != nil { + logger.Warnf("[ClientConfig CheckValidity] error: %v", err) + return + } + setClientGrpool() +} + +func setClientGrpool() { + if clientConf.GrPoolSize > 1 { + clientGrpool = gxsync.NewTaskPool(gxsync.WithTaskPoolTaskPoolSize(clientConf.GrPoolSize), gxsync.WithTaskPoolTaskQueueLength(clientConf.QueueLen), + gxsync.WithTaskPoolTaskQueueNumber(clientConf.QueueNumber)) + } +} + +// Options : param config +type Options struct { + // connect timeout + // remove request timeout, it will be calulate for every request + ConnectTimeout time.Duration +} + +// Client : some configuration for network communication. +type Client struct { + addr string + opts Options + conf ClientConfig + pool *gettyRPCClientPool + codec remoting.Codec + responseHandler remoting.ResponseHandler + ExchangeClient *remoting.ExchangeClient +} + +// create client +func NewClient(opt Options) *Client { + switch { + case opt.ConnectTimeout == 0: + opt.ConnectTimeout = 3 * time.Second + } + + c := &Client{ + opts: opt, + } + return c +} + +func (c *Client) SetExchangeClient(client *remoting.ExchangeClient) { + c.ExchangeClient = client +} +func (c *Client) SetResponseHandler(responseHandler remoting.ResponseHandler) { + c.responseHandler = responseHandler +} + +// init client and try to connection. +func (c *Client) Connect(url common.URL) error { + initClient(url.Protocol) + c.conf = *clientConf + // new client + c.pool = newGettyRPCClientConnPool(c, clientConf.PoolSize, time.Duration(int(time.Second)*clientConf.PoolTTL)) + // codec + c.codec = remoting.GetCodec(url.Protocol) + c.addr = url.Location + _, _, err := c.selectSession(c.addr) + if err != nil { + logger.Errorf("try to connect server %v failed for : %v", url.Location, err) + } + return err +} + +// close network connection +func (c *Client) Close() { + if c.pool != nil { + c.pool.close() + } + c.pool = nil +} + +// send request +func (c *Client) Request(request *remoting.Request, timeout time.Duration, response *remoting.PendingResponse) error { + _, session, err := c.selectSession(c.addr) + if err != nil { + return perrors.WithStack(err) + } + if session == nil { + return errSessionNotExist + } + + if err = c.transfer(session, request, timeout); err != nil { + return perrors.WithStack(err) + } + + if !request.TwoWay || response.Callback != nil { + return nil + } + + select { + case <-getty.GetTimeWheel().After(timeout): + return perrors.WithStack(errClientReadTimeout) + case <-response.Done: + err = response.Err + } + + return perrors.WithStack(err) +} + +func (c *Client) selectSession(addr string) (*gettyRPCClient, getty.Session, error) { + rpcClient, err := c.pool.getGettyRpcClient(addr) + if err != nil { + return nil, nil, perrors.WithStack(err) + } + return rpcClient, rpcClient.selectSession(), nil +} + +func (c *Client) heartbeat(session getty.Session) error { + req := remoting.NewRequest("2.0.2") + req.TwoWay = true + req.Event = true + resp := remoting.NewPendingResponse(req.ID) + remoting.AddPendingResponse(resp) + return c.transfer(session, req, 3*time.Second) +} + +func (c *Client) transfer(session getty.Session, request *remoting.Request, timeout time.Duration) error { + err := session.WritePkg(request, timeout) + return perrors.WithStack(err) +} diff --git a/remoting/getty/getty_client_test.go b/remoting/getty/getty_client_test.go new file mode 100644 index 0000000000000000000000000000000000000000..12726547d69e75912d1074af6f96b4427ed20ac8 --- /dev/null +++ b/remoting/getty/getty_client_test.go @@ -0,0 +1,497 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package getty + +import ( + "bytes" + "context" + "reflect" + "sync" + "testing" + "time" +) + +import ( + hessian "github.com/apache/dubbo-go-hessian2" + perrors "github.com/pkg/errors" + "github.com/stretchr/testify/assert" +) + +import ( + "github.com/apache/dubbo-go/common" + . "github.com/apache/dubbo-go/common/constant" + "github.com/apache/dubbo-go/common/proxy/proxy_factory" + "github.com/apache/dubbo-go/config" + "github.com/apache/dubbo-go/protocol" + "github.com/apache/dubbo-go/protocol/invocation" + "github.com/apache/dubbo-go/remoting" +) + +func TestRunSuite(t *testing.T) { + svr, url := InitTest(t) + client := getClient(url) + testRequestOneWay(t, svr, url, client) + testClient_Call(t, svr, url, client) + testClient_AsyncCall(t, svr, url, client) + + svr.Stop() +} + +func testRequestOneWay(t *testing.T, svr *Server, url common.URL, client *Client) { + + request := remoting.NewRequest("2.0.2") + up := &UserProvider{} + invocation := createInvocation("GetUser", nil, nil, []interface{}{[]interface{}{"1", "username"}, up}, + []reflect.Value{reflect.ValueOf([]interface{}{"1", "username"}), reflect.ValueOf(up)}) + attachment := map[string]string{INTERFACE_KEY: "com.ikurento.user.UserProvider"} + setAttachment(invocation, attachment) + request.Data = invocation + request.Event = false + request.TwoWay = false + //user := &User{} + err := client.Request(request, 3*time.Second, nil) + assert.NoError(t, err) +} + +func createInvocation(methodName string, callback interface{}, reply interface{}, arguments []interface{}, + parameterValues []reflect.Value) *invocation.RPCInvocation { + return invocation.NewRPCInvocationWithOptions(invocation.WithMethodName(methodName), + invocation.WithArguments(arguments), invocation.WithReply(reply), + invocation.WithCallBack(callback), invocation.WithParameterValues(parameterValues)) +} + +func setAttachment(invocation *invocation.RPCInvocation, attachments map[string]string) { + for key, value := range attachments { + invocation.SetAttachments(key, value) + } +} + +func getClient(url common.URL) *Client { + client := NewClient(Options{ + ConnectTimeout: config.GetConsumerConfig().ConnectTimeout, + }) + + exchangeClient := remoting.NewExchangeClient(url, client, 5*time.Second, false) + client.SetExchangeClient(exchangeClient) + client.Connect(url) + client.SetResponseHandler(exchangeClient) + return client +} + +func testClient_Call(t *testing.T, svr *Server, url common.URL, c *Client) { + c.pool = newGettyRPCClientConnPool(c, clientConf.PoolSize, time.Duration(int(time.Second)*clientConf.PoolTTL)) + + testGetBigPkg(t, c) + testGetUser(t, c) + testGetUser0(t, c) + testGetUser1(t, c) + testGetUser2(t, c) + testGetUser3(t, c) + testGetUser4(t, c) + testGetUser5(t, c) + testGetUser6(t, c) + testGetUser61(t, c) + +} +func testGetBigPkg(t *testing.T, c *Client) { + var ( + user *User + err error + ) + + user = &User{} + request := remoting.NewRequest("2.0.2") + invocation := createInvocation("GetBigPkg", nil, nil, []interface{}{[]interface{}{nil}, user}, + []reflect.Value{reflect.ValueOf([]interface{}{nil}), reflect.ValueOf(user)}) + attachment := map[string]string{INTERFACE_KEY: "com.ikurento.user.UserProvider"} + setAttachment(invocation, attachment) + request.Data = invocation + request.Event = false + request.TwoWay = true + pendingResponse := remoting.NewPendingResponse(request.ID) + pendingResponse.Reply = user + remoting.AddPendingResponse(pendingResponse) + err = c.Request(request, 8*time.Second, pendingResponse) + assert.NoError(t, err) + assert.NotEqual(t, "", user.Id) + assert.NotEqual(t, "", user.Name) +} +func testGetUser(t *testing.T, c *Client) { + var ( + user *User + err error + ) + user = &User{} + request := remoting.NewRequest("2.0.2") + invocation := createInvocation("GetUser", nil, nil, []interface{}{"1", "username"}, + []reflect.Value{reflect.ValueOf("1"), reflect.ValueOf("username")}) + attachment := map[string]string{INTERFACE_KEY: "com.ikurento.user.UserProvider"} + setAttachment(invocation, attachment) + request.Data = invocation + request.Event = false + request.TwoWay = true + pendingResponse := remoting.NewPendingResponse(request.ID) + pendingResponse.Reply = user + remoting.AddPendingResponse(pendingResponse) + err = c.Request(request, 3*time.Second, pendingResponse) + assert.NoError(t, err) + assert.Equal(t, User{Id: "1", Name: "username"}, *user) +} + +func testGetUser0(t *testing.T, c *Client) { + var ( + user *User + err error + ) + user = &User{} + request := remoting.NewRequest("2.0.2") + invocation := createInvocation("GetUser0", nil, nil, []interface{}{"1", nil, "username"}, + []reflect.Value{reflect.ValueOf("1"), reflect.ValueOf(nil), reflect.ValueOf("username")}) + attachment := map[string]string{INTERFACE_KEY: "com.ikurento.user.UserProvider"} + setAttachment(invocation, attachment) + request.Data = invocation + request.Event = false + request.TwoWay = true + rsp := remoting.NewPendingResponse(request.ID) + rsp.SetResponse(remoting.NewResponse(request.ID, "2.0.2")) + remoting.AddPendingResponse(rsp) + rsp.Reply = user + err = c.Request(request, 3*time.Second, rsp) + assert.NoError(t, err) + assert.Equal(t, User{Id: "1", Name: "username"}, *user) +} +func testGetUser1(t *testing.T, c *Client) { + var ( + err error + ) + request := remoting.NewRequest("2.0.2") + invocation := createInvocation("GetUser1", nil, nil, []interface{}{}, + []reflect.Value{}) + attachment := map[string]string{INTERFACE_KEY: "com.ikurento.user.UserProvider"} + setAttachment(invocation, attachment) + request.Data = invocation + request.Event = false + request.TwoWay = true + pendingResponse := remoting.NewPendingResponse(request.ID) + user := &User{} + pendingResponse.Reply = user + remoting.AddPendingResponse(pendingResponse) + err = c.Request(request, 3*time.Second, pendingResponse) + assert.NoError(t, err) +} +func testGetUser2(t *testing.T, c *Client) { + var ( + err error + ) + request := remoting.NewRequest("2.0.2") + invocation := createInvocation("GetUser2", nil, nil, []interface{}{}, + []reflect.Value{}) + attachment := map[string]string{INTERFACE_KEY: "com.ikurento.user.UserProvider"} + setAttachment(invocation, attachment) + request.Data = invocation + request.Event = false + request.TwoWay = true + pendingResponse := remoting.NewPendingResponse(request.ID) + remoting.AddPendingResponse(pendingResponse) + err = c.Request(request, 3*time.Second, pendingResponse) + assert.EqualError(t, err, "error") +} +func testGetUser3(t *testing.T, c *Client) { + var ( + err error + ) + request := remoting.NewRequest("2.0.2") + invocation := createInvocation("GetUser3", nil, nil, []interface{}{}, + []reflect.Value{}) + attachment := map[string]string{INTERFACE_KEY: "com.ikurento.user.UserProvider"} + setAttachment(invocation, attachment) + request.Data = invocation + request.Event = false + request.TwoWay = true + pendingResponse := remoting.NewPendingResponse(request.ID) + user2 := []interface{}{} + pendingResponse.Reply = &user2 + remoting.AddPendingResponse(pendingResponse) + err = c.Request(request, 3*time.Second, pendingResponse) + assert.NoError(t, err) + assert.Equal(t, &User{Id: "1", Name: "username"}, user2[0]) +} +func testGetUser4(t *testing.T, c *Client) { + var ( + err error + ) + request := remoting.NewRequest("2.0.2") + invocation := invocation.NewRPCInvocation("GetUser4", []interface{}{[]interface{}{"1", "username"}}, nil) + attachment := map[string]string{INTERFACE_KEY: "com.ikurento.user.UserProvider"} + setAttachment(invocation, attachment) + request.Data = invocation + request.Event = false + request.TwoWay = true + pendingResponse := remoting.NewPendingResponse(request.ID) + user2 := []interface{}{} + pendingResponse.Reply = &user2 + remoting.AddPendingResponse(pendingResponse) + err = c.Request(request, 3*time.Second, pendingResponse) + assert.NoError(t, err) + assert.Equal(t, &User{Id: "1", Name: "username"}, user2[0]) +} + +func testGetUser5(t *testing.T, c *Client) { + var ( + err error + ) + request := remoting.NewRequest("2.0.2") + invocation := invocation.NewRPCInvocation("GetUser5", []interface{}{map[interface{}]interface{}{"id": "1", "name": "username"}}, nil) + attachment := map[string]string{INTERFACE_KEY: "com.ikurento.user.UserProvider"} + setAttachment(invocation, attachment) + request.Data = invocation + request.Event = false + request.TwoWay = true + pendingResponse := remoting.NewPendingResponse(request.ID) + user3 := map[interface{}]interface{}{} + pendingResponse.Reply = &user3 + remoting.AddPendingResponse(pendingResponse) + err = c.Request(request, 3*time.Second, pendingResponse) + assert.NoError(t, err) + assert.NotNil(t, user3) + assert.Equal(t, &User{Id: "1", Name: "username"}, user3["key"]) +} + +func testGetUser6(t *testing.T, c *Client) { + var ( + user *User + err error + ) + user = &User{} + request := remoting.NewRequest("2.0.2") + invocation := invocation.NewRPCInvocation("GetUser6", []interface{}{0}, nil) + attachment := map[string]string{INTERFACE_KEY: "com.ikurento.user.UserProvider"} + setAttachment(invocation, attachment) + request.Data = invocation + request.Event = false + request.TwoWay = true + pendingResponse := remoting.NewPendingResponse(request.ID) + pendingResponse.Reply = user + remoting.AddPendingResponse(pendingResponse) + err = c.Request(request, 3*time.Second, pendingResponse) + assert.NoError(t, err) + assert.Equal(t, User{Id: "", Name: ""}, *user) +} + +func testGetUser61(t *testing.T, c *Client) { + var ( + user *User + err error + ) + user = &User{} + request := remoting.NewRequest("2.0.2") + invocation := invocation.NewRPCInvocation("GetUser6", []interface{}{1}, nil) + attachment := map[string]string{INTERFACE_KEY: "com.ikurento.user.UserProvider"} + setAttachment(invocation, attachment) + request.Data = invocation + request.Event = false + request.TwoWay = true + pendingResponse := remoting.NewPendingResponse(request.ID) + pendingResponse.Reply = user + remoting.AddPendingResponse(pendingResponse) + err = c.Request(request, 3*time.Second, pendingResponse) + assert.NoError(t, err) + assert.Equal(t, User{Id: "1", Name: ""}, *user) +} + +func testClient_AsyncCall(t *testing.T, svr *Server, url common.URL, client *Client) { + user := &User{} + lock := sync.Mutex{} + request := remoting.NewRequest("2.0.2") + invocation := createInvocation("GetUser0", nil, nil, []interface{}{"4", nil, "username"}, + []reflect.Value{reflect.ValueOf("4"), reflect.ValueOf(nil), reflect.ValueOf("username")}) + attachment := map[string]string{INTERFACE_KEY: "com.ikurento.user.UserProvider"} + setAttachment(invocation, attachment) + request.Data = invocation + request.Event = false + request.TwoWay = true + rsp := remoting.NewPendingResponse(request.ID) + rsp.SetResponse(remoting.NewResponse(request.ID, "2.0.2")) + remoting.AddPendingResponse(rsp) + rsp.Reply = user + rsp.Callback = func(response common.CallbackResponse) { + r := response.(remoting.AsyncCallbackResponse) + rst := *r.Reply.(*remoting.Response).Result.(*protocol.RPCResult) + assert.Equal(t, User{Id: "4", Name: "username"}, *(rst.Rest.(*User))) + lock.Unlock() + } + lock.Lock() + err := client.Request(request, 3*time.Second, rsp) + assert.NoError(t, err) + assert.Equal(t, User{}, *user) + time.Sleep(1 * time.Second) +} + +func InitTest(t *testing.T) (*Server, common.URL) { + + hessian.RegisterPOJO(&User{}) + remoting.RegistryCodec("dubbo", &DubboTestCodec{}) + + methods, err := common.ServiceMap.Register("", "dubbo", &UserProvider{}) + assert.NoError(t, err) + assert.Equal(t, "GetBigPkg,GetUser,GetUser0,GetUser1,GetUser2,GetUser3,GetUser4,GetUser5,GetUser6", methods) + + // config + SetClientConf(ClientConfig{ + ConnectionNum: 2, + HeartbeatPeriod: "5s", + SessionTimeout: "20s", + PoolTTL: 600, + PoolSize: 64, + GettySessionParam: GettySessionParam{ + CompressEncoding: false, + TcpNoDelay: true, + TcpKeepAlive: true, + KeepAlivePeriod: "120s", + TcpRBufSize: 262144, + TcpWBufSize: 65536, + PkgWQSize: 512, + TcpReadTimeout: "4s", + TcpWriteTimeout: "5s", + WaitTimeout: "1s", + MaxMsgLen: 10240000000, + SessionName: "client", + }, + }) + assert.NoError(t, clientConf.CheckValidity()) + SetServerConfig(ServerConfig{ + SessionNumber: 700, + SessionTimeout: "20s", + GettySessionParam: GettySessionParam{ + CompressEncoding: false, + TcpNoDelay: true, + TcpKeepAlive: true, + KeepAlivePeriod: "120s", + TcpRBufSize: 262144, + TcpWBufSize: 65536, + PkgWQSize: 512, + TcpReadTimeout: "1s", + TcpWriteTimeout: "5s", + WaitTimeout: "1s", + MaxMsgLen: 10240000000, + SessionName: "server", + }}) + assert.NoError(t, srvConf.CheckValidity()) + + url, err := common.NewURL("dubbo://127.0.0.1:20060/UserProvider?anyhost=true&" + + "application=BDTService&category=providers&default.timeout=10000&dubbo=dubbo-provider-golang-1.0.0&" + + "environment=dev&interface=com.ikurento.user.UserProvider&ip=127.0.0.1&methods=GetUser%2C&" + + "module=dubbogo+user-info+server&org=ikurento.com&owner=ZX&pid=1447&revision=0.0.1&" + + "side=provider&timeout=3000×tamp=1556509797245&bean.name=UserProvider") + // init server + userProvider := &UserProvider{} + common.ServiceMap.Register("", url.Protocol, userProvider) + invoker := &proxy_factory.ProxyInvoker{ + BaseInvoker: *protocol.NewBaseInvoker(url), + } + handler := func(invocation *invocation.RPCInvocation) protocol.RPCResult { + //result := protocol.RPCResult{} + r := invoker.Invoke(context.Background(), invocation) + result := protocol.RPCResult{ + Err: r.Error(), + Rest: r.Result(), + Attrs: r.Attachments(), + } + return result + } + server := NewServer(url, handler) + server.Start() + + time.Sleep(time.Second * 2) + + return server, url +} + +////////////////////////////////// +// provider +////////////////////////////////// + +type ( + User struct { + Id string `json:"id"` + Name string `json:"name"` + } + + UserProvider struct { + user map[string]User + } +) + +// size:4801228 +func (u *UserProvider) GetBigPkg(ctx context.Context, req []interface{}, rsp *User) error { + argBuf := new(bytes.Buffer) + for i := 0; i < 400; i++ { + argBuf.WriteString("鍑婚紦鍏堕晽锛岃笂璺冪敤鍏点€傚湡鍥藉煄婕曪紝鎴戠嫭鍗楄銆備粠瀛欏瓙浠诧紝骞抽檲涓庡畫銆備笉鎴戜互褰掞紝蹇у績鏈夊俊銆傜埌灞呯埌澶勶紵鐖颁抚鍏堕┈锛熶簬浠ユ眰涔嬶紵浜庢灄涔嬩笅銆傛鐢熷闃旓紝涓庡瓙鎴愯銆傛墽瀛愪箣鎵嬶紝涓庡瓙鍋曡€併€備簬鍡熼様鍏紝涓嶆垜娲诲叜銆備簬鍡熸吹鍏紝涓嶆垜淇″叜銆�") + argBuf.WriteString("鍑婚紦鍏堕晽锛岃笂璺冪敤鍏点€傚湡鍥藉煄婕曪紝鎴戠嫭鍗楄銆備粠瀛欏瓙浠诧紝骞抽檲涓庡畫銆備笉鎴戜互褰掞紝蹇у績鏈夊俊銆傜埌灞呯埌澶勶紵鐖颁抚鍏堕┈锛熶簬浠ユ眰涔嬶紵浜庢灄涔嬩笅銆傛鐢熷闃旓紝涓庡瓙鎴愯銆傛墽瀛愪箣鎵嬶紝涓庡瓙鍋曡€併€備簬鍡熼様鍏紝涓嶆垜娲诲叜銆備簬鍡熸吹鍏紝涓嶆垜淇″叜銆�") + } + rsp.Id = argBuf.String() + rsp.Name = argBuf.String() + return nil +} + +func (u *UserProvider) GetUser(ctx context.Context, req []interface{}, rsp *User) error { + rsp.Id = req[0].(string) + rsp.Name = req[1].(string) + return nil +} + +func (u *UserProvider) GetUser0(id string, k *User, name string) (User, error) { + return User{Id: id, Name: name}, nil +} + +func (u *UserProvider) GetUser1() error { + return nil +} + +func (u *UserProvider) GetUser2() error { + return perrors.New("error") +} + +func (u *UserProvider) GetUser3(rsp *[]interface{}) error { + *rsp = append(*rsp, User{Id: "1", Name: "username"}) + return nil +} + +func (u *UserProvider) GetUser4(ctx context.Context, req []interface{}) ([]interface{}, error) { + + return []interface{}{User{Id: req[0].([]interface{})[0].(string), Name: req[0].([]interface{})[1].(string)}}, nil +} + +func (u *UserProvider) GetUser5(ctx context.Context, req []interface{}) (map[interface{}]interface{}, error) { + return map[interface{}]interface{}{"key": User{Id: req[0].(map[interface{}]interface{})["id"].(string), Name: req[0].(map[interface{}]interface{})["name"].(string)}}, nil +} + +func (u *UserProvider) GetUser6(id int64) (*User, error) { + if id == 0 { + return nil, nil + } + return &User{Id: "1"}, nil +} + +func (u *UserProvider) Reference() string { + return "UserProvider" +} + +func (u User) JavaClassName() string { + return "com.ikurento.user.User" +} diff --git a/protocol/dubbo/impl/remoting/server_impl.go b/remoting/getty/getty_server.go similarity index 56% rename from protocol/dubbo/impl/remoting/server_impl.go rename to remoting/getty/getty_server.go index 6419ff685cda3390554f63477baed47c730a1b4e..6dc15d251f3b357f271ac420b6adc9ea194b957f 100644 --- a/protocol/dubbo/impl/remoting/server_impl.go +++ b/remoting/getty/getty_server.go @@ -14,7 +14,8 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package remoting + +package getty import ( "fmt" @@ -22,35 +23,74 @@ import ( ) import ( - "github.com/apache/dubbo-go/common" - "github.com/apache/dubbo-go/common/logger" + "github.com/dubbogo/getty" + gxsync "github.com/dubbogo/gost/sync" + "gopkg.in/yaml.v2" ) import ( - "github.com/dubbogo/getty" - gxsync "github.com/dubbogo/gost/sync" + "github.com/apache/dubbo-go/common" + "github.com/apache/dubbo-go/common/logger" + "github.com/apache/dubbo-go/config" + "github.com/apache/dubbo-go/protocol" + "github.com/apache/dubbo-go/protocol/invocation" + "github.com/apache/dubbo-go/remoting" ) -// TODO: 闇€瑕佺Щ鍔ㄥ埌 涓氬姟鐨勫疄鐜� var ( srvConf *ServerConfig srvGrpool *gxsync.TaskPool ) +func initServer(protocol string) { + + // load clientconfig from provider_config + // default use dubbo + providerConfig := config.GetProviderConfig() + if providerConfig.ApplicationConfig == nil { + return + } + protocolConf := providerConfig.ProtocolConf + defaultServerConfig := GetDefaultServerConfig() + if protocolConf == nil { + logger.Info("protocol_conf default use dubbo config") + } else { + dubboConf := protocolConf.(map[interface{}]interface{})[protocol] + if dubboConf == nil { + logger.Warnf("dubboConf is nil") + return + } + + dubboConfByte, err := yaml.Marshal(dubboConf) + if err != nil { + panic(err) + } + err = yaml.Unmarshal(dubboConfByte, &defaultServerConfig) + if err != nil { + panic(err) + } + } + srvConf = &defaultServerConfig + if err := srvConf.CheckValidity(); err != nil { + panic(err) + } + SetServerGrpool() +} + // SetServerConfig ... func SetServerConfig(s ServerConfig) { srvConf = &s err := srvConf.CheckValidity() if err != nil { - panic(err) + logger.Warnf("[ServerConfig CheckValidity] error: %v", err) return } SetServerGrpool() } // GetServerConfig ... -func GetServerConfig() *ServerConfig { - return srvConf +func GetServerConfig() ServerConfig { + return *srvConf } // SetServerGrpool ... @@ -63,19 +103,27 @@ func SetServerGrpool() { // Server ... type Server struct { - conf ServerConfig - tcpServer getty.Server - rpcHandler *RpcServerHandler + conf ServerConfig + addr string + codec remoting.Codec + tcpServer getty.Server + rpcHandler *RpcServerHandler + requestHandler func(*invocation.RPCInvocation) protocol.RPCResult } // NewServer ... -func NewServer(handler StubHandler) *Server { +func NewServer(url common.URL, handlers func(*invocation.RPCInvocation) protocol.RPCResult) *Server { + //init + initServer(url.Protocol) s := &Server{ - conf: *srvConf, + conf: *srvConf, + addr: url.Location, + codec: remoting.GetCodec(url.Protocol), + requestHandler: handlers, } - s.rpcHandler = NewRpcServerHandler(handler, s.conf.SessionNumber, s.conf.SessionTimeoutD) + s.rpcHandler = NewRpcServerHandler(s.conf.SessionNumber, s.conf.sessionTimeout, s) return s } @@ -98,20 +146,20 @@ func (s *Server) newSession(session getty.Session) error { tcpConn.SetNoDelay(conf.GettySessionParam.TcpNoDelay) tcpConn.SetKeepAlive(conf.GettySessionParam.TcpKeepAlive) if conf.GettySessionParam.TcpKeepAlive { - tcpConn.SetKeepAlivePeriod(conf.GettySessionParam.KeepAlivePeriodD) + tcpConn.SetKeepAlivePeriod(conf.GettySessionParam.keepAlivePeriod) } tcpConn.SetReadBuffer(conf.GettySessionParam.TcpRBufSize) tcpConn.SetWriteBuffer(conf.GettySessionParam.TcpWBufSize) session.SetName(conf.GettySessionParam.SessionName) session.SetMaxMsgLen(conf.GettySessionParam.MaxMsgLen) - session.SetPkgHandler(rpcServerPkgHandler) + session.SetPkgHandler(NewRpcServerPackageHandler(s)) session.SetEventListener(s.rpcHandler) session.SetWQLen(conf.GettySessionParam.PkgWQSize) - session.SetReadTimeout(conf.GettySessionParam.TcpReadTimeoutD) - session.SetWriteTimeout(conf.GettySessionParam.TcpWriteTimeoutD) - session.SetCronPeriod((int)(conf.SessionTimeoutD.Nanoseconds() / 1e6)) - session.SetWaitTime(conf.GettySessionParam.WaitTimeoutD) + session.SetReadTimeout(conf.GettySessionParam.tcpReadTimeout) + session.SetWriteTimeout(conf.GettySessionParam.tcpWriteTimeout) + session.SetCronPeriod((int)(conf.sessionTimeout.Nanoseconds() / 1e6)) + session.SetWaitTime(conf.GettySessionParam.waitTimeout) logger.Debugf("app accepts new session:%s\n", session.Stat()) session.SetTaskPool(srvGrpool) @@ -120,18 +168,16 @@ func (s *Server) newSession(session getty.Session) error { } // Start ... -func (s *Server) Start(url common.URL) { +func (s *Server) Start() { var ( - addr string tcpServer getty.Server ) - addr = url.Location tcpServer = getty.NewTCPServer( - getty.WithLocalAddress(addr), + getty.WithLocalAddress(s.addr), ) tcpServer.RunEventLoop(s.newSession) - logger.Debugf("s bind addr{%s} ok!", addr) + logger.Debugf("s bind addr{%s} ok!", s.addr) s.tcpServer = tcpServer } diff --git a/protocol/dubbo/impl/remoting/server_listener.go b/remoting/getty/listener.go similarity index 54% rename from protocol/dubbo/impl/remoting/server_listener.go rename to remoting/getty/listener.go index f59e88e3f31a1067a27c133e8f7c8bdca8a76137..777e14c2ecb344d3fa2aa2084b79c7805f542e28 100644 --- a/protocol/dubbo/impl/remoting/server_listener.go +++ b/remoting/getty/listener.go @@ -15,9 +15,10 @@ * limitations under the License. */ -package remoting +package getty import ( + "fmt" "sync" "sync/atomic" "time" @@ -30,13 +31,16 @@ import ( ) import ( + "github.com/apache/dubbo-go/common/constant" "github.com/apache/dubbo-go/common/logger" - "github.com/apache/dubbo-go/protocol/dubbo/impl" + "github.com/apache/dubbo-go/protocol/invocation" + "github.com/apache/dubbo-go/remoting" ) -// todo: writePkg_Timeout will entry *.yml +// todo: WritePkg_Timeout will entry *.yml const ( - writePkg_Timeout = 5 * time.Second + // WritePkg_Timeout ... + WritePkg_Timeout = 5 * time.Second ) var ( @@ -48,12 +52,10 @@ type rpcSession struct { reqNum int32 } -// AddReqNum adds total request number safely func (s *rpcSession) AddReqNum(num int32) { atomic.AddInt32(&s.reqNum, num) } -// GetReqNum gets total request number safely func (s *rpcSession) GetReqNum() int32 { return atomic.LoadInt32(&s.reqNum) } @@ -62,74 +64,81 @@ func (s *rpcSession) GetReqNum() int32 { // RpcClientHandler // ////////////////////////////////////////// -// RpcClientHandler is handler of RPC Client +// RpcClientHandler ... type RpcClientHandler struct { conn *gettyRPCClient } -// NewRpcClientHandler creates RpcClientHandler with @gettyRPCClient +// NewRpcClientHandler ... func NewRpcClientHandler(client *gettyRPCClient) *RpcClientHandler { return &RpcClientHandler{conn: client} } -// OnOpen notified when RPC client session opened +// OnOpen ... func (h *RpcClientHandler) OnOpen(session getty.Session) error { h.conn.addSession(session) return nil } -// OnError notified when RPC client session got any error +// OnError ... func (h *RpcClientHandler) OnError(session getty.Session, err error) { - logger.Warnf("session{%s} got error{%v}, will be closed.", session.Stat(), err) + logger.Infof("session{%s} got error{%v}, will be closed.", session.Stat(), err) h.conn.removeSession(session) } -// OnOpen notified when RPC client session closed +// OnClose ... func (h *RpcClientHandler) OnClose(session getty.Session) { logger.Infof("session{%s} is closing......", session.Stat()) h.conn.removeSession(session) } -// OnMessage notified when RPC client session got any message in connection +// OnMessage ... func (h *RpcClientHandler) OnMessage(session getty.Session, pkg interface{}) { - p, ok := pkg.(*impl.DubboPackage) + result, ok := pkg.(remoting.DecodeResult) if !ok { logger.Errorf("illegal package") return } - - if p.Header.Type&impl.PackageHeartbeat != 0x00 { - logger.Debugf("get rpc heartbeat response{header: %#v, body: %#v}", p.Header, p.Body) - if p.Err != nil { - logger.Errorf("rpc heartbeat response{error: %#v}", p.Err) + // get heartbeart request from server + if result.IsRequest { + req := result.Result.(*remoting.Request) + if req.Event { + logger.Debugf("get rpc heartbeat request{%#v}", req) + resp := remoting.NewResponse(req.ID, req.Version) + resp.Status = hessian.Response_OK + resp.Event = req.Event + resp.SerialID = req.SerialID + resp.Version = "2.0.2" + reply(session, resp, hessian.PackageHeartbeat) + return } + logger.Errorf("illegal request but not heartbeart. {%#v}", req) return } - logger.Debugf("get rpc response{header: %#v, body: %#v}", p.Header, p.Body) - - h.conn.updateSession(session) - pendingResponse := h.conn.pool.rpcClient.removePendingResponse(impl.SequenceType(p.Header.ID)) - if pendingResponse == nil { - logger.Errorf("failed to get pending response context for response package %s", *p) + p := result.Result.(*remoting.Response) + // get heartbeart + if p.Event { + logger.Debugf("get rpc heartbeat response{%#v}", p) + if p.Error != nil { + logger.Errorf("rpc heartbeat response{error: %#v}", p.Error) + } + h.conn.pool.rpcClient.responseHandler.Handler(p) return } - - if p.Err != nil { - pendingResponse.err = p.Err + if result.IsRequest { + logger.Errorf("illegal package for it is response type. {%#v}", pkg) + return } - pendingResponse.response.Atta = p.Body.(*Response).Atta + logger.Debugf("get rpc response{%#v}", p) - if pendingResponse.callback == nil { - pendingResponse.done <- struct{}{} - } else { - logger.Info("proxy service callback") - pendingResponse.callback(pendingResponse.GetCallResponse()) - } + h.conn.updateSession(session) + + h.conn.pool.rpcClient.responseHandler.Handler(p) } -// OnCron notified when RPC client session got any message in cron job +// OnCron ... func (h *RpcClientHandler) OnCron(session getty.Session) { rpcSession, err := h.conn.getClientRpcSession(session) if err != nil { @@ -137,10 +146,10 @@ func (h *RpcClientHandler) OnCron(session getty.Session) { session.Stat(), perrors.WithStack(err)) return } - if h.conn.pool.rpcClient.Conf.SessionTimeoutD.Nanoseconds() < time.Since(session.GetActive()).Nanoseconds() { + if h.conn.pool.rpcClient.conf.sessionTimeout.Nanoseconds() < time.Since(session.GetActive()).Nanoseconds() { logger.Warnf("session{%s} timeout{%s}, reqNum{%d}", session.Stat(), time.Since(session.GetActive()).String(), rpcSession.reqNum) - h.conn.removeSession(session) // -> h.conn.close() -> h.conn.Pool.remove(h.conn) + h.conn.removeSession(session) // -> h.conn.close() -> h.conn.pool.remove(h.conn) return } @@ -151,35 +160,26 @@ func (h *RpcClientHandler) OnCron(session getty.Session) { // RpcServerHandler // ////////////////////////////////////////// -type StubHandler interface { - OnPackage(session getty.Session, pkg *impl.DubboPackage) -} - -type StubFunc func(session getty.Session, pkg *impl.DubboPackage) - -func (f StubFunc) OnPackage(session getty.Session, pkg *impl.DubboPackage) { - f(session, pkg) -} - +// RpcServerHandler ... type RpcServerHandler struct { maxSessionNum int sessionTimeout time.Duration sessionMap map[getty.Session]*rpcSession rwlock sync.RWMutex - stub StubHandler + server *Server } -// NewRpcServerHandler creates RpcServerHandler with @maxSessionNum and @sessionTimeout -func NewRpcServerHandler(stubHandler StubHandler, maxSessionNum int, sessionTimeout time.Duration) *RpcServerHandler { +// NewRpcServerHandler ... +func NewRpcServerHandler(maxSessionNum int, sessionTimeout time.Duration, serverP *Server) *RpcServerHandler { return &RpcServerHandler{ maxSessionNum: maxSessionNum, sessionTimeout: sessionTimeout, sessionMap: make(map[getty.Session]*rpcSession), - stub: stubHandler, + server: serverP, } } -// OnOpen notified when RPC server session opened +// OnOpen ... func (h *RpcServerHandler) OnOpen(session getty.Session) error { var err error h.rwlock.RLock() @@ -198,15 +198,15 @@ func (h *RpcServerHandler) OnOpen(session getty.Session) error { return nil } -// OnError notified when RPC server session got any error +// OnError ... func (h *RpcServerHandler) OnError(session getty.Session, err error) { - logger.Warnf("session{%s} got error{%v}, will be closed.", session.Stat(), err) + logger.Infof("session{%s} got error{%v}, will be closed.", session.Stat(), err) h.rwlock.Lock() delete(h.sessionMap, session) h.rwlock.Unlock() } -// OnOpen notified when RPC server session closed +// OnClose ... func (h *RpcServerHandler) OnClose(session getty.Session) { logger.Infof("session{%s} is closing......", session.Stat()) h.rwlock.Lock() @@ -214,7 +214,7 @@ func (h *RpcServerHandler) OnClose(session getty.Session) { h.rwlock.Unlock() } -// OnMessage notified when RPC server session got any message in connection +// OnMessage ... func (h *RpcServerHandler) OnMessage(session getty.Session, pkg interface{}) { h.rwlock.Lock() if _, ok := h.sessionMap[session]; ok { @@ -222,54 +222,70 @@ func (h *RpcServerHandler) OnMessage(session getty.Session, pkg interface{}) { } h.rwlock.Unlock() - p, ok := pkg.(*impl.DubboPackage) + decodeResult, ok := pkg.(remoting.DecodeResult) if !ok { logger.Errorf("illegal package{%#v}", pkg) return } - p.SetResponseStatus(hessian.Response_OK) - //p.Header.ResponseStatus = hessian.Response_OK - - // heartbeat - if p.GetHeader().Type&impl.PackageHeartbeat != 0x00 { - logger.Debugf("get rpc heartbeat request{header: %#v, service: %#v, body: %#v}", p.GetHeader(), p.GetService(), p.GetBody()) - h.reply(session, p, impl.PackageHeartbeat) + if !decodeResult.IsRequest { + logger.Errorf("illegal package for it is response type. {%#v}", pkg) return } + req := decodeResult.Result.(*remoting.Request) + + resp := remoting.NewResponse(req.ID, req.Version) + resp.Status = hessian.Response_OK + resp.Event = req.Event + resp.SerialID = req.SerialID + resp.Version = "2.0.2" - twoway := true - // not twoway - if p.GetHeader().Type&impl.PackageRequest_TwoWay == 0x00 { - twoway = false + // heartbeat + if req.Event { + logger.Debugf("get rpc heartbeat request{%#v}", resp) + reply(session, resp, hessian.PackageHeartbeat) + return } defer func() { if e := recover(); e != nil { - p.SetResponseStatus(hessian.Response_SERVER_ERROR) + resp.Status = hessian.Response_SERVER_ERROR if err, ok := e.(error); ok { logger.Errorf("OnMessage panic: %+v", perrors.WithStack(err)) - p.SetBody(perrors.WithStack(err)) + resp.Error = perrors.WithStack(err) } else if err, ok := e.(string); ok { logger.Errorf("OnMessage panic: %+v", perrors.New(err)) - p.SetBody(perrors.New(err)) + resp.Error = perrors.New(err) } else { logger.Errorf("OnMessage panic: %+v, this is impossible.", e) - p.SetBody(e) + resp.Error = fmt.Errorf("OnMessage panic unknow exception. %+v", e) } - if !twoway { + if !req.TwoWay { return } - h.reply(session, p, impl.PackageResponse) + reply(session, resp, hessian.PackageResponse) } }() - h.stub.OnPackage(session, p) - h.reply(session, p, impl.PackageResponse) + invoc, ok := req.Data.(*invocation.RPCInvocation) + if !ok { + panic("create invocation occur some exception for the type is not suitable one.") + return + } + attachments := invoc.Attachments() + attachments[constant.LOCAL_ADDR] = session.LocalAddr() + attachments[constant.REMOTE_ADDR] = session.RemoteAddr() + + result := h.server.requestHandler(invoc) + if !req.TwoWay { + return + } + resp.Result = result + reply(session, resp, hessian.PackageResponse) } -// OnCron notified when RPC server session got any message in cron job +// OnCron ... func (h *RpcServerHandler) OnCron(session getty.Session) { var ( flag bool @@ -295,35 +311,9 @@ func (h *RpcServerHandler) OnCron(session getty.Session) { } } -func (h *RpcServerHandler) reply(session getty.Session, req *impl.DubboPackage, tp impl.PackageType) { - header := impl.DubboHeader{ - SerialID: req.GetHeader().SerialID, - Type: tp, - ID: req.GetHeader().ID, - BodyLen: 0, - ResponseStatus: req.GetHeader().ResponseStatus, - } - resp := NewServerResponsePackage(header) - if err := impl.LoadSerializer(resp); err != nil { - logger.Errorf("Reply error %v", err) - return - } - - if req.GetHeader().Type&impl.PackageRequest != 0x00 { - resp.SetBody(req.GetBody()) - } - - if err := session.WritePkg(resp, writePkg_Timeout); err != nil { - logger.Errorf("WritePkg error: %#v, %#v", perrors.WithStack(err), req.GetHeader()) - } -} +func reply(session getty.Session, resp *remoting.Response, tp hessian.PackageType) { -// server side response package, just for serialization -func NewServerResponsePackage(header impl.DubboHeader) *impl.DubboPackage { - return &impl.DubboPackage{ - Header: header, - Body: nil, - Err: nil, - Codec: impl.NewDubboCodec(nil), + if err := session.WritePkg(resp, WritePkg_Timeout); err != nil { + logger.Errorf("WritePkg error: %#v, %#v", perrors.WithStack(err), resp) } } diff --git a/protocol/dubbo/server_test.go b/remoting/getty/listener_test.go similarity index 74% rename from protocol/dubbo/server_test.go rename to remoting/getty/listener_test.go index aa7d75056f8bae405c22a1caa5f7224c17360015..da460aec1430f8ecb2c2cb84fc6978b92ba9be58 100644 --- a/protocol/dubbo/server_test.go +++ b/remoting/getty/listener_test.go @@ -15,9 +15,10 @@ * limitations under the License. */ -package dubbo +package getty import ( + "context" "testing" ) @@ -56,3 +57,18 @@ func TestRebuildCtx(t *testing.T) { assert.NotNil(t, ctx) assert.NotNil(t, ctx.Value(constant.TRACING_REMOTE_SPAN_CTX)) } + +// rebuildCtx rebuild the context by attachment. +// Once we decided to transfer more context's key-value, we should change this. +// now we only support rebuild the tracing context +func rebuildCtx(inv *invocation.RPCInvocation) context.Context { + ctx := context.WithValue(context.Background(), "attachment", inv.Attachments()) + + // actually, if user do not use any opentracing framework, the err will not be nil. + spanCtx, err := opentracing.GlobalTracer().Extract(opentracing.TextMap, + opentracing.TextMapCarrier(inv.Attachments())) + if err == nil { + ctx = context.WithValue(ctx, constant.TRACING_REMOTE_SPAN_CTX, spanCtx) + } + return ctx +} diff --git a/protocol/dubbo/impl/remoting/pool.go b/remoting/getty/pool.go similarity index 81% rename from protocol/dubbo/impl/remoting/pool.go rename to remoting/getty/pool.go index dc8b1238cc1b8f6ae8aee2e4b42186ae7d6e24bc..fdefa238202f0b650c9b7cf3389b81cca2f1c80d 100644 --- a/protocol/dubbo/impl/remoting/pool.go +++ b/remoting/getty/pool.go @@ -15,11 +15,10 @@ * limitations under the License. */ -package remoting +package getty import ( "fmt" - "math/rand" "net" "sync" @@ -37,10 +36,10 @@ import ( ) type gettyRPCClient struct { - once sync.Once - protocol string - addr string - active int64 // zero, not create or be destroyed + once sync.Once + //protocol string + addr string + active int64 // zero, not create or be destroyed pool *gettyRPCClientPool @@ -50,23 +49,23 @@ type gettyRPCClient struct { } var ( - errClientPoolClosed = perrors.New("client Pool closed") + errClientPoolClosed = perrors.New("client pool closed") ) -func newGettyRPCClientConn(pool *gettyRPCClientPool, protocol, addr string) (*gettyRPCClient, error) { +func newGettyRPCClientConn(pool *gettyRPCClientPool, addr string) (*gettyRPCClient, error) { c := &gettyRPCClient{ - protocol: protocol, - addr: addr, - pool: pool, + //protocol: protocol, + addr: addr, + pool: pool, gettyClient: getty.NewTCPClient( getty.WithServerAddress(addr), - getty.WithConnectionNumber((int)(pool.rpcClient.Conf.ConnectionNum)), - getty.WithReconnectInterval(pool.rpcClient.Conf.ReconnectInterval), + getty.WithConnectionNumber((int)(pool.rpcClient.conf.ConnectionNum)), + getty.WithReconnectInterval(pool.rpcClient.conf.ReconnectInterval), ), } go c.gettyClient.RunEventLoop(c.newSession) idx := 1 - times := int(pool.rpcClient.Opts.ConnectTimeout / 1e6) + times := int(pool.rpcClient.opts.ConnectTimeout / 1e6) for { idx++ if c.isAvailable() { @@ -99,8 +98,7 @@ func (c *gettyRPCClient) newSession(session getty.Session) error { tcpConn *net.TCPConn conf ClientConfig ) - - conf = c.pool.rpcClient.Conf + conf = c.pool.rpcClient.conf if conf.GettySessionParam.CompressEncoding { session.SetCompressType(getty.CompressZip) } @@ -112,7 +110,7 @@ func (c *gettyRPCClient) newSession(session getty.Session) error { tcpConn.SetNoDelay(conf.GettySessionParam.TcpNoDelay) tcpConn.SetKeepAlive(conf.GettySessionParam.TcpKeepAlive) if conf.GettySessionParam.TcpKeepAlive { - tcpConn.SetKeepAlivePeriod(conf.GettySessionParam.KeepAlivePeriodD) + tcpConn.SetKeepAlivePeriod(conf.GettySessionParam.keepAlivePeriod) } tcpConn.SetReadBuffer(conf.GettySessionParam.TcpRBufSize) tcpConn.SetWriteBuffer(conf.GettySessionParam.TcpWBufSize) @@ -122,10 +120,10 @@ func (c *gettyRPCClient) newSession(session getty.Session) error { session.SetPkgHandler(NewRpcClientPackageHandler(c.pool.rpcClient)) session.SetEventListener(NewRpcClientHandler(c)) session.SetWQLen(conf.GettySessionParam.PkgWQSize) - session.SetReadTimeout(conf.GettySessionParam.TcpReadTimeoutD) - session.SetWriteTimeout(conf.GettySessionParam.TcpWriteTimeoutD) - session.SetCronPeriod((int)(conf.HeartbeatPeriodD.Nanoseconds() / 1e6)) - session.SetWaitTime(conf.GettySessionParam.WaitTimeoutD) + session.SetReadTimeout(conf.GettySessionParam.tcpReadTimeout) + session.SetWriteTimeout(conf.GettySessionParam.tcpWriteTimeout) + session.SetCronPeriod((int)(conf.heartbeatPeriod.Nanoseconds() / 1e6)) + session.SetWaitTime(conf.GettySessionParam.waitTimeout) logger.Debugf("client new session:%s\n", session.Stat()) session.SetTaskPool(clientGrpool) @@ -220,25 +218,25 @@ func (c *gettyRPCClient) updateSession(session getty.Session) { func (c *gettyRPCClient) getClientRpcSession(session getty.Session) (rpcSession, error) { var ( - err error - rpcClientSession rpcSession + err error + rpcSession rpcSession ) c.lock.RLock() defer c.lock.RUnlock() if c.sessions == nil { - return rpcClientSession, errClientClosed + return rpcSession, errClientClosed } err = errSessionNotExist for _, s := range c.sessions { if s.session == session { - rpcClientSession = *s + rpcSession = *s err = nil break } } - return rpcClientSession, perrors.WithStack(err) + return rpcSession, perrors.WithStack(err) } func (c *gettyRPCClient) isAvailable() bool { @@ -297,12 +295,13 @@ type gettyRPCClientPool struct { conns []*gettyRPCClient } -func NewGettyRPCClientConnPool(rpcClient *Client, size int, ttl time.Duration) *gettyRPCClientPool { +func newGettyRPCClientConnPool(rpcClient *Client, size int, ttl time.Duration) *gettyRPCClientPool { return &gettyRPCClientPool{ rpcClient: rpcClient, size: size, ttl: int64(ttl.Seconds()), - conns: make([]*gettyRPCClient, 0, 16), + // init capacity : 2 + conns: make([]*gettyRPCClient, 0, 2), } } @@ -316,11 +315,15 @@ func (p *gettyRPCClientPool) close() { } } -func (p *gettyRPCClientPool) getGettyRpcClient(protocol, addr string) (*gettyRPCClient, error) { +func (p *gettyRPCClientPool) getGettyRpcClient(addr string) (*gettyRPCClient, error) { conn, err := p.get() if err == nil && conn == nil { // create new conn - conn, err = newGettyRPCClientConn(p, protocol, addr) + rpcClientConn, err := newGettyRPCClientConn(p, addr) + if err == nil { + p.put(rpcClientConn) + } + return rpcClientConn, perrors.WithStack(err) } return conn, perrors.WithStack(err) } @@ -333,10 +336,15 @@ func (p *gettyRPCClientPool) get() (*gettyRPCClient, error) { if p.conns == nil { return nil, errClientPoolClosed } - - for len(p.conns) > 0 { - conn := p.conns[len(p.conns)-1] - p.conns = p.conns[:len(p.conns)-1] + for num := len(p.conns); num > 0; { + var conn *gettyRPCClient + if num != 1 { + conn = p.conns[rand.Int31n(int32(num))] + } else { + conn = p.conns[0] + } + // This will recreate gettyRpcClient for remove last position + //p.conns = p.conns[:len(p.conns)-1] if d := now - conn.getActive(); d > p.ttl { p.remove(conn) @@ -353,23 +361,19 @@ func (p *gettyRPCClientPool) put(conn *gettyRPCClient) { if conn == nil || conn.getActive() == 0 { return } - p.Lock() defer p.Unlock() - if p.conns == nil { return } - // check whether @conn has existed in p.conns or not. for i := range p.conns { if p.conns[i] == conn { return } } - if len(p.conns) >= p.size { - // delete @conn from client Pool + // delete @conn from client pool // p.remove(conn) conn.close() return diff --git a/protocol/dubbo/impl/remoting/readwriter.go b/remoting/getty/readwriter.go similarity index 50% rename from protocol/dubbo/impl/remoting/readwriter.go rename to remoting/getty/readwriter.go index 6d211f5ea5e6e9365d396370a9f0c102f7fa6183..c8105fad0f47bab9c216c06500dac86946ad2017 100644 --- a/protocol/dubbo/impl/remoting/readwriter.go +++ b/remoting/getty/readwriter.go @@ -15,12 +15,9 @@ * limitations under the License. */ -// TODO: zero.xu readwrite 涓皢client/server handler 鍒嗗紑 -package remoting +package getty import ( - "bufio" - "bytes" "reflect" ) @@ -32,7 +29,7 @@ import ( import ( "github.com/apache/dubbo-go/common/logger" - "github.com/apache/dubbo-go/protocol/dubbo/impl" + "github.com/apache/dubbo-go/remoting" ) //////////////////////////////////////////// @@ -50,130 +47,89 @@ func NewRpcClientPackageHandler(client *Client) *RpcClientPackageHandler { } func (p *RpcClientPackageHandler) Read(ss getty.Session, data []byte) (interface{}, int, error) { - pkg := NewClientResponsePackage(data) - if err := pkg.ReadHeader(); err != nil { - originErr := perrors.Cause(err) - if originErr == hessian.ErrHeaderNotEnough || originErr == hessian.ErrBodyNotEnough { + //pkg := &DubboPackage{} + //p.client.ExchangeClient.GetPendingResponse(remoting.SequenceType()) + resp, length, err := (p.client.codec).Decode(data) + //err := pkg.Unmarshal(buf, p.client) + if err != nil { + if err == hessian.ErrHeaderNotEnough || err == hessian.ErrBodyNotEnough { return nil, 0, nil } - logger.Errorf("[RpcClientPackageHandler.Read] ss:%+v, len(@data):%d) = error:%+v ", ss, len(data), err) - return nil, 0, perrors.WithStack(err) - } - if pkg.IsHeartBeat() { - // heartbeat package doesn't need deserialize - return pkg, pkg.GetLen(), nil - } - if err := impl.LoadSerializer(pkg); err != nil { - return nil, 0, err - } + logger.Errorf("pkg.Unmarshal(ss:%+v, len(@data):%d) = error:%+v", ss, len(data), err) - // load response - pendingRsp, ok := p.client.PendingResponses.Load(impl.SequenceType(pkg.GetHeader().ID)) - if !ok { - return nil, 0, perrors.Errorf("client.GetPendingResopnse(%v) = nil", pkg.GetHeader().ID) - } - // set package body - body := impl.NewResponsePayload(pendingRsp.(*PendingResponse).response.Reply, nil, nil) - pkg.SetBody(body) - err := pkg.Unmarshal() - if err != nil { - return nil, 0, perrors.WithStack(err) + return nil, length, err } - resp := pkg.Body.(*impl.ResponsePayload) - pkg.Err = resp.Exception - pkg.Body = NewResponse(resp.RspObj, resp.Attachments) - return pkg, pkg.GetLen(), nil + //if pkg.Header.Type&hessian.PackageRequest == 0x00 { + // pkg.Err = pkg.Body.(*hessian.Response).Exception + // pkg.Body = NewResponse(pkg.Body.(*hessian.Response).RspObj, pkg.Body.(*hessian.Response).Attachments) + //} + + return resp, length, nil } func (p *RpcClientPackageHandler) Write(ss getty.Session, pkg interface{}) ([]byte, error) { - req, ok := pkg.(*impl.DubboPackage) + req, ok := pkg.(*remoting.Request) if !ok { + logger.Errorf("illegal pkg:%+v\n", pkg) return nil, perrors.New("invalid rpc request") } - buf, err := req.Marshal() + + buf, err := (p.client.codec).EncodeRequest(req) if err != nil { logger.Warnf("binary.Write(req{%#v}) = err{%#v}", req, perrors.WithStack(err)) return nil, perrors.WithStack(err) } - return buf.Bytes(), nil -} -func NewClientResponsePackage(data []byte) *impl.DubboPackage { - return &impl.DubboPackage{ - Header: impl.DubboHeader{}, - Service: impl.Service{}, - Body: &impl.ResponsePayload{}, - Err: nil, - Codec: impl.NewDubboCodec(bufio.NewReaderSize(bytes.NewBuffer(data), len(data))), - } + return buf.Bytes(), nil } //////////////////////////////////////////// // RpcServerPackageHandler //////////////////////////////////////////// -var ( - rpcServerPkgHandler = &RpcServerPackageHandler{} -) +//var ( +// rpcServerPkgHandler = &RpcServerPackageHandler{} +//) // RpcServerPackageHandler ... type RpcServerPackageHandler struct { + server *Server } -func (p *RpcServerPackageHandler) Read(ss getty.Session, data []byte) (interface{}, int, error) { - pkg := NewServerRequestPackage(data) - if err := pkg.ReadHeader(); err != nil { - originErr := perrors.Cause(err) - if originErr == hessian.ErrHeaderNotEnough || originErr == hessian.ErrBodyNotEnough { - return nil, 0, nil - } - return nil, 0, perrors.WithStack(err) - } - - if pkg.IsHeartBeat() { - return pkg, pkg.GetLen(), nil - } +func NewRpcServerPackageHandler(server *Server) *RpcServerPackageHandler { + return &RpcServerPackageHandler{server: server} +} - if err := impl.LoadSerializer(pkg); err != nil { - return nil, 0, err - } +func (p *RpcServerPackageHandler) Read(ss getty.Session, data []byte) (interface{}, int, error) { + req, length, err := (p.server.codec).Decode(data) + //resp,len, err := (*p.).DecodeResponse(buf) - err := pkg.Unmarshal() if err != nil { - originErr := perrors.Cause(err) - if originErr == hessian.ErrHeaderNotEnough || originErr == hessian.ErrBodyNotEnough { + if err == hessian.ErrHeaderNotEnough || err == hessian.ErrBodyNotEnough { return nil, 0, nil } logger.Errorf("pkg.Unmarshal(ss:%+v, len(@data):%d) = error:%+v", ss, len(data), err) - return nil, 0, perrors.WithStack(err) + + return nil, 0, err } - return pkg, pkg.GetLen(), nil + + return req, length, err } func (p *RpcServerPackageHandler) Write(ss getty.Session, pkg interface{}) ([]byte, error) { - res, ok := pkg.(*impl.DubboPackage) + res, ok := pkg.(*remoting.Response) if !ok { logger.Errorf("illegal pkg:%+v\n, it is %+v", pkg, reflect.TypeOf(pkg)) return nil, perrors.New("invalid rpc response") } - buf, err := res.Marshal() + + buf, err := (p.server.codec).EncodeResponse(res) if err != nil { logger.Warnf("binary.Write(res{%#v}) = err{%#v}", res, perrors.WithStack(err)) return nil, perrors.WithStack(err) } - return buf.Bytes(), nil -} - -// server side receive request package, just for deserialization -func NewServerRequestPackage(data []byte) *impl.DubboPackage { - return &impl.DubboPackage{ - Header: impl.DubboHeader{}, - Service: impl.Service{}, - Body: make([]interface{}, 7), - Err: nil, - Codec: impl.NewDubboCodec(bufio.NewReaderSize(bytes.NewBuffer(data), len(data))), - } + return buf.Bytes(), nil }