diff --git a/.gitignore b/.gitignore index fabff68b87..8158b497e3 100644 --- a/.gitignore +++ b/.gitignore @@ -29,6 +29,8 @@ config_center/zookeeper/zookeeper-4unittest/ registry/zookeeper/zookeeper-4unittest/ metadata/report/zookeeper/zookeeper-4unittest/ registry/consul/agent* +metadata/report/consul/agent* +remoting/consul/agent* config_center/apollo/mockDubbog.properties.json # vim stuff diff --git a/common/constant/default.go b/common/constant/default.go index 629aa32392..4165942a61 100644 --- a/common/constant/default.go +++ b/common/constant/default.go @@ -45,6 +45,7 @@ const ( DEFAULT_REST_CLIENT = "resty" DEFAULT_REST_SERVER = "go-restful" DEFAULT_PORT = 20000 + DEFAULT_SERIALIZATION = HESSIAN2_SERIALIZATION ) const ( diff --git a/common/constant/key.go b/common/constant/key.go index fe8c5baaf0..943338f8e6 100644 --- a/common/constant/key.go +++ b/common/constant/key.go @@ -22,31 +22,32 @@ const ( ) const ( - PORT_KEY = "port" - GROUP_KEY = "group" - VERSION_KEY = "version" - INTERFACE_KEY = "interface" - PATH_KEY = "path" - PROTOCOL_KEY = "protocol" - 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" - PATH_SEPARATOR = "/" - DUBBO_KEY = "dubbo" - RELEASE_KEY = "release" - ANYHOST_KEY = "anyhost" - SSL_ENABLED_KEY = "ssl-enabled" + 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" + PORT_KEY = "port" + PROTOCOL_KEY = "protocol" + PATH_SEPARATOR = "/" + DUBBO_KEY = "dubbo" + SSL_ENABLED_KEY = "ssl-enabled" ) const ( @@ -81,6 +82,7 @@ const ( EXECUTE_REJECTED_EXECUTION_HANDLER_KEY = "execute.limit.rejected.handler" PROVIDER_SHUTDOWN_FILTER = "pshutdown" CONSUMER_SHUTDOWN_FILTER = "cshutdown" + SERIALIZATION_KEY = "serialization" PID_KEY = "pid" SYNC_REPORT_KEY = "sync.report" RETRY_PERIOD_KEY = "retry.period" diff --git a/common/constant/serializtion.go b/common/constant/serializtion.go new file mode 100644 index 0000000000..f27598ccf5 --- /dev/null +++ b/common/constant/serializtion.go @@ -0,0 +1,28 @@ +/* + * 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 constant + +const ( + S_Hessian2 byte = 2 + S_Proto byte = 21 +) + +const ( + HESSIAN2_SERIALIZATION = "hessian2" + PROTOBUF_SERIALIZATION = "protobuf" +) diff --git a/common/url.go b/common/url.go index 0ff3e8f034..5a3e57406b 100644 --- a/common/url.go +++ b/common/url.go @@ -18,6 +18,7 @@ package common import ( + "bytes" "encoding/base64" "fmt" "math" @@ -325,12 +326,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("/") @@ -338,7 +342,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/service_config.go b/config/service_config.go index 8968bac88f..48632a1b1e 100644 --- a/config/service_config.go +++ b/config/service_config.go @@ -59,6 +59,7 @@ type ServiceConfig struct { Methods []*MethodConfig `yaml:"methods" json:"methods,omitempty" property:"methods"` Warmup string `yaml:"warmup" json:"warmup,omitempty" property:"warmup"` Retries string `yaml:"retries" json:"retries,omitempty" property:"retries"` + Serialization string `yaml:"serialization" json:"serialization" property:"serialization"` Params map[string]string `yaml:"params" json:"params,omitempty" property:"params"` Token string `yaml:"token" json:"token,omitempty" property:"token"` AccessLog string `yaml:"accesslog" json:"accesslog,omitempty" property:"accesslog"` @@ -270,7 +271,8 @@ func (c *ServiceConfig) getUrlMap() url.Values { urlMap.Set(constant.ROLE_KEY, strconv.Itoa(common.PROVIDER)) urlMap.Set(constant.RELEASE_KEY, "dubbo-golang-"+constant.Version) urlMap.Set(constant.SIDE_KEY, (common.RoleType(common.PROVIDER)).Role()) - + // todo: move + urlMap.Set(constant.SERIALIZATION_KEY, c.Serialization) // application info urlMap.Set(constant.APPLICATION_KEY, providerConfig.ApplicationConfig.Name) urlMap.Set(constant.ORGANIZATION_KEY, providerConfig.ApplicationConfig.Organization) diff --git a/metadata/service/exporter/configurable/exporter_test.go b/metadata/service/exporter/configurable/exporter_test.go index 9fdbd76757..b304b9153f 100644 --- a/metadata/service/exporter/configurable/exporter_test.go +++ b/metadata/service/exporter/configurable/exporter_test.go @@ -30,15 +30,15 @@ import ( "github.com/apache/dubbo-go/config" _ "github.com/apache/dubbo-go/filter/filter_impl" "github.com/apache/dubbo-go/metadata/service/inmemory" - "github.com/apache/dubbo-go/protocol/dubbo" _ "github.com/apache/dubbo-go/protocol/dubbo" + "github.com/apache/dubbo-go/remoting/getty" ) func TestConfigurableExporter(t *testing.T) { - dubbo.SetServerConfig(dubbo.ServerConfig{ + getty.SetServerConfig(getty.ServerConfig{ SessionNumber: 700, SessionTimeout: "20s", - GettySessionParam: dubbo.GettySessionParam{ + GettySessionParam: getty.GettySessionParam{ CompressEncoding: false, TcpNoDelay: true, TcpKeepAlive: true, diff --git a/protocol/dubbo/client.go b/protocol/dubbo/client.go deleted file mode 100644 index 8c71c57939..0000000000 --- a/protocol/dubbo/client.go +++ /dev/null @@ -1,362 +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" - "strings" - "sync" - "time" -) - -import ( - "github.com/apache/dubbo-getty" - gxsync "github.com/dubbogo/gost/sync" - perrors "github.com/pkg/errors" - "go.uber.org/atomic" - "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/dubbo/hessian2" -) - -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 -) - -func init() { - // 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{})[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 - } - setClientGrpool() - - rand.Seed(time.Now().UnixNano()) -} - -// 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 -} - -// 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, - } - 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]interface{} -} - -// NewRequest create a new Request. -func NewRequest(addr string, svcUrl common.URL, method string, args interface{}, atta map[string]interface{}) *Request { - 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]interface{} -} - -// NewResponse creates a new Response. -func NewResponse(reply interface{}, atta map[string]interface{}) *Response { - return &Response{ - reply: reply, - atta: atta, - } -} - -// CallOneway call by one way -func (c *Client) CallOneway(request *Request) error { - return perrors.WithStack(c.call(CT_OneWay, request, NewResponse(nil, nil), nil)) -} - -// Call call remoting by two way or one way, if @response.reply is nil, the way of call is one way. -func (c *Client) Call(request *Request, response *Response) error { - ct := CT_TwoWay - if response.reply == nil { - ct = CT_OneWay - } - - return perrors.WithStack(c.call(ct, request, response, nil)) -} - -// AsyncCall call remoting by async with callback. -func (c *Client) AsyncCall(request *Request, callback common.AsyncCallback, response *Response) error { - return perrors.WithStack(c.call(CT_TwoWay, request, response, callback)) -} - -func (c *Client) call(ct CallType, request *Request, response *Response, callback common.AsyncCallback) error { - p := &DubboPackage{} - p.Service.Path = strings.TrimPrefix(request.svcUrl.Path, "/") - p.Service.Interface = request.svcUrl.GetParam(constant.INTERFACE_KEY, "") - p.Service.Version = request.svcUrl.GetParam(constant.VERSION_KEY, "") - p.Service.Group = request.svcUrl.GetParam(constant.GROUP_KEY, "") - p.Service.Method = request.method - c.pool.sslEnabled = request.svcUrl.GetParamBool(constant.SSL_ENABLED_KEY, false) - - p.Service.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 { - p.Service.Timeout = t - } - } - - p.Header.SerialID = byte(S_Dubbo) - p.Body = hessian2.NewRequest(request.args, request.atta) - - var rsp *PendingResponse - if ct != CT_OneWay { - p.Header.Type = hessian2.PackageRequest_TwoWay - rsp = NewPendingResponse() - rsp.response = response - rsp.callback = callback - } else { - p.Header.Type = hessian2.PackageRequest - } - - 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() - }() - - if err = c.transfer(session, p, rsp); err != nil { - return perrors.WithStack(err) - } - - if ct == CT_OneWay || callback != nil { - return nil - } - - select { - case <-getty.GetTimeWheel().After(c.opts.RequestTimeout): - c.removePendingResponse(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(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 *DubboPackage, rsp *PendingResponse) error { - var ( - sequence uint64 - err error - ) - - sequence = c.sequence.Add(1) - - if pkg == nil { - pkg = &DubboPackage{} - pkg.Body = hessian2.NewRequest([]interface{}{}, nil) - pkg.Body = []interface{}{} - pkg.Header.Type = hessian2.PackageHeartbeat - pkg.Header.SerialID = byte(S_Dubbo) - } - pkg.Header.ID = int64(sequence) - - // cond1 - if rsp != nil { - rsp.seq = sequence - c.addPendingResponse(rsp) - } - - err = session.WritePkg(pkg, c.opts.RequestTimeout) - if err != nil { - c.removePendingResponse(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(SequenceType(pr.seq), pr) -} - -func (c *Client) removePendingResponse(seq 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 -} diff --git a/protocol/dubbo/client_test.go b/protocol/dubbo/client_test.go deleted file mode 100644 index a3b194ad40..0000000000 --- a/protocol/dubbo/client_test.go +++ /dev/null @@ -1,335 +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" - "fmt" - "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" -) - -const ( - mockMethodNameGetUser = "GetUser" - mockMethodNameGetBigPkg = "GetBigPkg" - mockAddress = "127.0.0.1:20000" -) - -func TestClientCallOneway(t *testing.T) { - proto, url := InitTest(t) - - c := &Client{ - pendingResponses: new(sync.Map), - conf: *clientConf, - opts: Options{ - ConnectTimeout: 3e9, - RequestTimeout: 6e9, - }, - } - c.pool = newGettyRPCClientConnPool(c, clientConf.PoolSize, time.Duration(int(time.Second)*clientConf.PoolTTL)) - - err := c.CallOneway(NewRequest(mockAddress, url, mockMethodNameGetUser, []interface{}{"1", "username"}, nil)) - assert.NoError(t, err) - - // destroy - proto.Destroy() -} - -func TestClientCall(t *testing.T) { - proto, url := InitTest(t) - - c := &Client{ - pendingResponses: new(sync.Map), - conf: *clientConf, - opts: Options{ - ConnectTimeout: 3e9, - RequestTimeout: 10e9, - }, - } - c.pool = newGettyRPCClientConnPool(c, clientConf.PoolSize, time.Duration(int(time.Second)*clientConf.PoolTTL)) - - var ( - user *User - err error - ) - - user = &User{} - err = c.Call(NewRequest(mockAddress, url, mockMethodNameGetBigPkg, []interface{}{nil}, nil), NewResponse(user, nil)) - assert.NoError(t, err) - assert.NotEqual(t, "", user.Id) - assert.NotEqual(t, "", user.Name) - - user = &User{} - err = c.Call(NewRequest(mockAddress, url, mockMethodNameGetUser, []interface{}{"1", "username"}, nil), NewResponse(user, nil)) - assert.NoError(t, err) - assert.Equal(t, User{Id: "1", Name: "username"}, *user) - - user = &User{} - err = c.Call(NewRequest(mockAddress, url, "GetUser0", []interface{}{"1", nil, "username"}, nil), NewResponse(user, nil)) - assert.NoError(t, err) - assert.Equal(t, User{Id: "1", Name: "username"}, *user) - - err = c.Call(NewRequest(mockAddress, url, "GetUser1", []interface{}{}, nil), NewResponse(user, nil)) - assert.NoError(t, err) - - err = c.Call(NewRequest(mockAddress, url, "GetUser2", []interface{}{}, nil), NewResponse(user, nil)) - assert.EqualError(t, err, "error") - - user2 := []interface{}{} - err = c.Call(NewRequest(mockAddress, url, "GetUser3", []interface{}{}, nil), NewResponse(&user2, nil)) - assert.NoError(t, err) - assert.Equal(t, &User{Id: "1", Name: "username"}, user2[0]) - - user2 = []interface{}{} - err = c.Call(NewRequest(mockAddress, url, "GetUser4", []interface{}{[]interface{}{"1", "username"}}, nil), NewResponse(&user2, nil)) - assert.NoError(t, err) - assert.Equal(t, &User{Id: "1", Name: "username"}, user2[0]) - - user3 := map[interface{}]interface{}{} - err = c.Call(NewRequest(mockAddress, url, "GetUser5", []interface{}{map[interface{}]interface{}{"id": "1", "name": "username"}}, nil), 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(NewRequest(mockAddress, url, "GetUser6", []interface{}{0}, nil), NewResponse(user, nil)) - assert.NoError(t, err) - assert.Equal(t, User{Id: "", Name: ""}, *user) - - user = &User{} - err = c.Call(NewRequest(mockAddress, url, "GetUser6", []interface{}{1}, nil), NewResponse(user, nil)) - assert.NoError(t, err) - assert.Equal(t, User{Id: "1", Name: ""}, *user) - - user = &User{} - r1 := "v1" - r2 := &AttaTestObject{Ti: 45, Desc: "v2"} - err = c.Call(NewRequest(mockAddress, url, "GetUserForAttachment", []interface{}{1}, map[string]interface{}{"sim": r1, "comp": r2}), NewResponse(user, nil)) - assert.NoError(t, err) - // the param is transfered from client to server by attachment, and the server will return the attachment value. - // the test should check the value came back from server. - assert.Equal(t, User{Id: r1, Name: fmt.Sprintf("%+v", *r2)}, *user) - - // destroy - proto.Destroy() -} - -func TestClientAsyncCall(t *testing.T) { - proto, url := InitTest(t) - - c := &Client{ - pendingResponses: new(sync.Map), - conf: *clientConf, - opts: Options{ - ConnectTimeout: 3e9, - RequestTimeout: 6e9, - }, - } - c.pool = newGettyRPCClientConnPool(c, clientConf.PoolSize, time.Duration(int(time.Second)*clientConf.PoolTTL)) - - user := &User{} - lock := sync.Mutex{} - lock.Lock() - err := c.AsyncCall(NewRequest(mockAddress, url, mockMethodNameGetUser, []interface{}{"1", "username"}, nil), func(response common.CallbackResponse) { - r := response.(AsyncCallbackResponse) - assert.Equal(t, User{Id: "1", Name: "username"}, *r.Reply.(*Response).reply.(*User)) - lock.Unlock() - }, 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{}) - hessian.RegisterPOJO(&AttaTestObject{}) - - 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,GetUserForAttachment", 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()) - - // 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) GetUserForAttachment(context context.Context, id int64) (*User, error) { - if id == 0 { - return nil, nil - } - var attachments = context.Value("attachment").(map[string]interface{}) - Id := attachments["sim"].(string) - name := fmt.Sprintf("%+v", *(attachments["comp"].(*AttaTestObject))) - return &User{Id: Id, Name: name}, nil -} - -func (u *UserProvider) Reference() string { - return "UserProvider" -} - -func (u User) JavaClassName() string { - return "com.ikurento.user.User" -} - -type AttaTestObject struct { - Ti int64 - Desc string -} - -func (u *AttaTestObject) JavaClassName() string { - return "UserProvider" -} diff --git a/protocol/dubbo/codec.go b/protocol/dubbo/codec.go deleted file mode 100644 index c33c92dfd9..0000000000 --- a/protocol/dubbo/codec.go +++ /dev/null @@ -1,160 +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 ( - "bufio" - "bytes" - "fmt" - "time" -) - -import ( - "github.com/apache/dubbo-go/common" - perrors "github.com/pkg/errors" -) - -import ( - "github.com/apache/dubbo-go/protocol/dubbo/hessian2" -) - -//SerialID serial ID -type SerialID byte - -const ( - // S_Dubbo dubbo serial id - S_Dubbo SerialID = 2 -) - -//CallType call type -type CallType int32 - -const ( - // CT_UNKNOWN unknown call type - CT_UNKNOWN CallType = 0 - // CT_OneWay call one way - CT_OneWay CallType = 1 - // CT_TwoWay call in request/response - CT_TwoWay CallType = 2 -) - -//////////////////////////////////////////// -// dubbo package -//////////////////////////////////////////// - -// SequenceType sequence type -type SequenceType int64 - -// nolint -type DubboPackage struct { - Header hessian2.DubboHeader - Service hessian2.Service - Body interface{} - Err error -} - -// String prints dubbo package detail include header、path、body etc. -func (p DubboPackage) String() string { - return fmt.Sprintf("DubboPackage: Header-%v, Path-%v, Body-%v", p.Header, p.Service, p.Body) -} - -// Marshal encode hessian package. -func (p *DubboPackage) Marshal() (*bytes.Buffer, error) { - codec := hessian2.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 decodes hessian package. -func (p *DubboPackage) Unmarshal(buf *bytes.Buffer, opts ...interface{}) error { - // fix issue https://github.com/apache/dubbo-go/issues/380 - bufLen := buf.Len() - if bufLen < hessian2.HEADER_LENGTH { - return perrors.WithStack(hessian2.ErrHeaderNotEnough) - } - - codec := hessian2.NewHessianCodec(bufio.NewReaderSize(buf, bufLen)) - - // read header - err := codec.ReadHeader(&p.Header) - if err != nil { - return perrors.WithStack(err) - } - - if len(opts) != 0 { // for client - client, ok := opts[0].(*Client) - if !ok { - return perrors.Errorf("opts[0] is not of type *Client") - } - - if p.Header.Type&hessian2.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, ok := client.pendingResponses.Load(SequenceType(p.Header.ID)) - if !ok { - return perrors.Errorf("client.GetPendingResponse(%v) = nil", p.Header.ID) - } - p.Body = &hessian2.DubboResponse{RspObj: pendingRsp.(*PendingResponse).response.reply} - } - } - - // read body - err = codec.ReadBody(p.Body) - return perrors.WithStack(err) -} - -//////////////////////////////////////////// -// PendingResponse -//////////////////////////////////////////// - -// PendingResponse is a pending response. -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, - } -} diff --git a/protocol/dubbo/dubbo_codec.go b/protocol/dubbo/dubbo_codec.go new file mode 100644 index 0000000000..5e859a7fa2 --- /dev/null +++ b/protocol/dubbo/dubbo_codec.go @@ -0,0 +1,290 @@ +/* + * 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" + "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/dubbo/impl" + "github.com/apache/dubbo-go/protocol/invocation" + "github.com/apache/dubbo-go/remoting" +) + +//SerialID serial ID +type SerialID byte + +func init() { + codec := &DubboCodec{} + // this is for registry dubboCodec of dubbo protocol + remoting.RegistryCodec("dubbo", codec) +} + +// 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 { + err := perrors.Errorf("encode request failed for parameter type :%+v", request) + logger.Errorf(err.Error()) + return nil, err + } + 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.PROTOBUF_SERIALIZATION { + header.SerialID = constant.S_Proto + } else { + header.SerialID = constant.S_Hessian2 + } + 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 = &impl.ResponsePayload{ + 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) + } + + 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.(map[string]interface{}) + + //invocation := request.Data.(*invocation.RPCInvocation) + var methodName string + var args []interface{} + attachments := make(map[string]interface{}) + if req[impl.DubboVersionKey] != nil { + //dubbo version + request.Version = req[impl.DubboVersionKey].(string) + } + //path + attachments[constant.PATH_KEY] = pkg.Service.Path + //version + attachments[constant.VERSION_KEY] = pkg.Service.Version + //method + methodName = pkg.Service.Method + args = req[impl.ArgsKey].([]interface{}) + attachments = req[impl.AttachmentsKey].(map[string]interface{}) + 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.(*impl.ResponsePayload).Exception != nil { + rpcResult.Err = pkg.Body.(*impl.ResponsePayload).Exception + response.Error = rpcResult.Err + } + rpcResult.Attrs = pkg.Body.(*impl.ResponsePayload).Attachments + rpcResult.Rest = pkg.Body.(*impl.ResponsePayload).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 983a05dcec..bce33508be 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,8 +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" invocation_impl "github.com/apache/dubbo-go/protocol/invocation" + "github.com/apache/dubbo-go/remoting" ) var ( @@ -46,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 *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 create dubbo client invoker. -func NewDubboInvoker(url common.URL, client *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, } } @@ -84,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) @@ -94,35 +110,57 @@ func (di *DubboInvoker) Invoke(ctx context.Context, invocation protocol.Invocati di.appendCtx(ctx, inv) url := di.GetUrl() + // default hessian2 serialization, compatible + if url.GetParam(constant.SERIALIZATION_KEY, "") == "" { + url.SetParam(constant.SERIALIZATION_KEY, constant.HESSIAN2_SERIALIZATION) + } // async async, err := strconv.ParseBool(inv.AttachmentsByKey(constant.ASYNC_KEY, "false")) if err != nil { logger.Errorf("ParseBool - error: %v", err) async = false } - response := 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(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(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(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 bf352c082c..4d32c29a22 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,40 +26,37 @@ 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/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 TestDubboInvokerInvoke(t *testing.T) { proto, url := InitTest(t) - c := &Client{ - pendingResponses: new(sync.Map), - conf: *clientConf, - opts: Options{ - ConnectTimeout: 3 * time.Second, - RequestTimeout: 6 * time.Second, - }, - } - c.pool = newGettyRPCClientConnPool(c, clientConf.PoolSize, time.Duration(int(time.Second)*clientConf.PoolTTL)) + c := getExchangeClient(url) invoker := NewDubboInvoker(url, c) user := &User{} - inv := invocation.NewRPCInvocationWithOptions(invocation.WithMethodName(mockMethodNameGetUser), invocation.WithArguments([]interface{}{"1", "username"}), + inv := invocation.NewRPCInvocationWithOptions(invocation.WithMethodName("GetUser"), invocation.WithArguments([]interface{}{"1", "username"}), invocation.WithReply(user), invocation.WithAttachments(map[string]interface{}{"test_key": "test_value"})) // Call 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") @@ -69,8 +67,10 @@ func TestDubboInvokerInvoke(t *testing.T) { lock := sync.Mutex{} lock.Lock() inv.SetCallBack(func(response common.CallbackResponse) { - r := response.(AsyncCallbackResponse) - assert.Equal(t, User{Id: "1", Name: "username"}, *r.Reply.(*Response).reply.(*User)) + r := response.(remoting.AsyncCallbackResponse) + 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) @@ -92,3 +92,143 @@ func TestDubboInvokerInvoke(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 9eeefd0792..8dda52b6b9 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,14 +35,23 @@ 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/invocation" + "github.com/apache/dubbo-go/remoting" + "github.com/apache/dubbo-go/remoting/getty" ) -// dubbo protocol constant const ( // DUBBO is dubbo protocol name DUBBO = "dubbo" ) +var ( + // Make the connection can be shared. + // It will create one connection for one address (ip+port) + exchangeClientMap = new(sync.Map) + exchangeLock = new(sync.Map) +) + func init() { extension.SetProtocol(DUBBO, GetProtocol) } @@ -45,10 +60,12 @@ 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]*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 } @@ -56,7 +73,7 @@ type DubboProtocol struct { func NewDubboProtocol() *DubboProtocol { return &DubboProtocol{ BaseProtocol: protocol.NewBaseProtocol(), - serverMap: make(map[string]*Server), + serverMap: make(map[string]*remoting.ExchangeServer), } } @@ -67,7 +84,6 @@ func (dp *DubboProtocol) Export(invoker protocol.Invoker) protocol.Exporter { exporter := NewDubboExporter(serviceKey, invoker, dp.ExporterMap()) dp.SetExporterMap(serviceKey, exporter) logger.Infof("Export service: %s", url.String()) - // start server dp.openServer(url) return exporter @@ -75,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, NewClient(Options{ - ConnectTimeout: config.GetConsumerConfig().ConnectTimeout, - RequestTimeout: requestTimeout, - })) + invoker := NewDubboInvoker(url, exchangeClient) dp.SetInvokers(invoker) logger.Infof("Refer service: %s", url.String()) return invoker @@ -116,9 +126,12 @@ func (dp *DubboProtocol) openServer(url common.URL) { dp.serverLock.Lock() _, ok = dp.serverMap[url.Location] if !ok { - srv := NewServer() + 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() } @@ -131,3 +144,91 @@ 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, + RequestTimeout: config.GetConsumerConfig().RequestTimeout, + }), 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 + } + return clientTmp.(*remoting.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(filterContext(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 6f3892be67..9eba90e9da 100644 --- a/protocol/dubbo/dubbo_protocol_test.go +++ b/protocol/dubbo/dubbo_protocol_test.go @@ -28,7 +28,9 @@ import ( 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" + "github.com/apache/dubbo-go/remoting/getty" ) const ( @@ -39,14 +41,56 @@ const ( "side=provider&timeout=3000×tamp=1556509797245" ) -func TestDubboProtocolExport(t *testing.T) { +func initDubboInvokerTest() { + 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) { + initDubboInvokerTest() + srvCfg := getty.GetDefaultServerConfig() + getty.SetServerConfig(srvCfg) // Export proto := GetProtocol() - srvConf = &ServerConfig{} url, err := common.NewURL(mockCommonUrl) assert.NoError(t, err) exporter := proto.Export(protocol.NewBaseInvoker(url)) - // make sure url eq := exporter.GetInvoker().GetUrl().URLEqual(url) assert.True(t, eq) @@ -60,10 +104,10 @@ func TestDubboProtocolExport(t *testing.T) { assert.True(t, eq2) // make sure exporterMap after 'Unexport' - _, ok := proto.(*DubboProtocol).ExporterMap().Load(url.ServiceKey()) + _, ok := proto.(*DubboProtocol).ExporterMap().Load(url2.ServiceKey()) assert.True(t, ok) - exporter.Unexport() - _, ok = proto.(*DubboProtocol).ExporterMap().Load(url.ServiceKey()) + exporter2.Unexport() + _, ok = proto.(*DubboProtocol).ExporterMap().Load(url2.ServiceKey()) assert.False(t, ok) // make sure serverMap after 'Destroy' @@ -74,14 +118,29 @@ func TestDubboProtocolExport(t *testing.T) { assert.False(t, ok) } -func TestDubboProtocolRefer(t *testing.T) { +func TestDubboProtocolReferNoConnect(t *testing.T) { // Refer + initDubboInvokerTest() proto := GetProtocol() url, err := common.NewURL(mockCommonUrl) assert.NoError(t, err) - clientConf = &ClientConfig{} invoker := proto.Refer(url) + assert.Nil(t, invoker) +} + +func TestDubboProtocol_Refer(t *testing.T) { + initDubboInvokerTest() + cliCfg := getty.GetDefaultClientConfig() + getty.SetClientConf(cliCfg) + // Refer + proto := GetProtocol() + url, err := common.NewURL(mockCommonUrl) + proto.Export(&proxy_factory.ProxyInvoker{ + BaseInvoker: *protocol.NewBaseInvoker(url), + }) + assert.NoError(t, err) + invoker := proto.Refer(url) // make sure url eq := invoker.GetUrl().URLEqual(url) assert.True(t, eq) diff --git a/protocol/dubbo/impl/codec.go b/protocol/dubbo/impl/codec.go new file mode 100644 index 0000000000..c139f3547b --- /dev/null +++ b/protocol/dubbo/impl/codec.go @@ -0,0 +1,291 @@ +/* + * 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 impl + +import ( + "bufio" + "encoding/binary" +) + +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/remoting" +) + +type ProtocolCodec struct { + reader *bufio.Reader + pkgType PackageType + bodyLen int + serializer Serializer + headerRead bool +} + +func (c *ProtocolCodec) ReadHeader(header *DubboHeader) error { + var err error + if c.reader.Size() < HEADER_LENGTH { + return hessian.ErrHeaderNotEnough + } + buf, err := c.reader.Peek(HEADER_LENGTH) + if err != nil { // this is impossible + return perrors.WithStack(err) + } + _, err = c.reader.Discard(HEADER_LENGTH) + if err != nil { // this is impossible + return perrors.WithStack(err) + } + + //// read header + if buf[0] != MAGIC_HIGH && buf[1] != MAGIC_LOW { + return hessian.ErrIllegalPackage + } + + // Header{serialization id(5 bit), event, two way, req/response} + if header.SerialID = buf[2] & SERIAL_MASK; header.SerialID == Zero { + return perrors.Errorf("serialization ID:%v", header.SerialID) + } + + flag := buf[2] & FLAG_EVENT + if flag != Zero { + header.Type |= PackageHeartbeat + } + flag = buf[2] & FLAG_REQUEST + if flag != Zero { + header.Type |= PackageRequest + flag = buf[2] & FLAG_TWOWAY + if flag != Zero { + header.Type |= PackageRequest_TwoWay + } + } else { + header.Type |= PackageResponse + header.ResponseStatus = buf[3] + if header.ResponseStatus != Response_OK { + header.Type |= PackageResponse_Exception + } + } + + // Header{req id} + header.ID = int64(binary.BigEndian.Uint64(buf[4:])) + + // Header{body len} + header.BodyLen = int(binary.BigEndian.Uint32(buf[12:])) + if header.BodyLen < 0 { + return hessian.ErrIllegalPackage + } + + c.pkgType = header.Type + c.bodyLen = header.BodyLen + + if c.reader.Buffered() < c.bodyLen { + return hessian.ErrBodyNotEnough + } + c.headerRead = true + return perrors.WithStack(err) +} + +func (c *ProtocolCodec) EncodeHeader(p DubboPackage) []byte { + header := p.Header + bs := make([]byte, 0) + switch header.Type { + case PackageHeartbeat: + if header.ResponseStatus == Zero { + bs = append(bs, hessian.DubboRequestHeartbeatHeader[:]...) + } else { + bs = append(bs, hessian.DubboResponseHeartbeatHeader[:]...) + } + case PackageResponse: + bs = append(bs, hessian.DubboResponseHeaderBytes[:]...) + if header.ResponseStatus != 0 { + bs[3] = header.ResponseStatus + } + case PackageRequest_TwoWay: + bs = append(bs, hessian.DubboRequestHeaderBytesTwoWay[:]...) + } + bs[2] |= header.SerialID & hessian.SERIAL_MASK + binary.BigEndian.PutUint64(bs[4:], uint64(header.ID)) + return bs +} + +func (c *ProtocolCodec) Encode(p DubboPackage) ([]byte, error) { + // header + if c.serializer == nil { + return nil, perrors.New("serializer should not be nil") + } + header := p.Header + switch header.Type { + case PackageHeartbeat: + if header.ResponseStatus == Zero { + return packRequest(p, c.serializer) + } + return packResponse(p, c.serializer) + + case PackageRequest, PackageRequest_TwoWay: + return packRequest(p, c.serializer) + + case PackageResponse: + return packResponse(p, c.serializer) + + default: + return nil, perrors.Errorf("Unrecognised message type: %v", header.Type) + } +} + +func (c *ProtocolCodec) Decode(p *DubboPackage) error { + if !c.headerRead { + if err := c.ReadHeader(&p.Header); err != nil { + return err + } + } + body, err := c.reader.Peek(p.GetBodyLen()) + if err != nil { + return err + } + if p.IsResponseWithException() { + logger.Infof("response with exception: %+v", p.Header) + decoder := hessian.NewDecoder(body) + exception, err := decoder.Decode() + if err != nil { + return perrors.WithStack(err) + } + rsp, ok := p.Body.(*ResponsePayload) + if !ok { + return perrors.Errorf("java exception:%s", exception.(string)) + } + rsp.Exception = perrors.Errorf("java exception:%s", exception.(string)) + return nil + } else if p.IsHeartBeat() { + // heartbeat no need to unmarshal contents + return nil + } + if c.serializer == nil { + return perrors.New("Codec serializer is nil") + } + if p.IsResponse() { + p.Body = &ResponsePayload{ + RspObj: remoting.GetPendingResponse(remoting.SequenceType(p.Header.ID)).Reply, + } + } + return c.serializer.Unmarshal(body, p) +} + +func (c *ProtocolCodec) SetSerializer(serializer Serializer) { + c.serializer = serializer +} + +func packRequest(p DubboPackage, serializer Serializer) ([]byte, error) { + var ( + byteArray []byte + pkgLen int + ) + + header := p.Header + + ////////////////////////////////////////// + // byteArray + ////////////////////////////////////////// + // magic + switch header.Type { + case PackageHeartbeat: + byteArray = append(byteArray, DubboRequestHeartbeatHeader[:]...) + case PackageRequest_TwoWay: + byteArray = append(byteArray, DubboRequestHeaderBytesTwoWay[:]...) + default: + byteArray = append(byteArray, DubboRequestHeaderBytes[:]...) + } + + // serialization id, two way flag, event, request/response flag + // SerialID is id of serialization approach in java dubbo + byteArray[2] |= header.SerialID & SERIAL_MASK + // request id + binary.BigEndian.PutUint64(byteArray[4:], uint64(header.ID)) + + ////////////////////////////////////////// + // body + ////////////////////////////////////////// + if p.IsHeartBeat() { + byteArray = append(byteArray, byte('N')) + pkgLen = 1 + } else { + body, err := serializer.Marshal(p) + if err != nil { + return nil, err + } + pkgLen = len(body) + if pkgLen > int(DEFAULT_LEN) { // 8M + return nil, perrors.Errorf("Data length %d too large, max payload %d", pkgLen, DEFAULT_LEN) + } + byteArray = append(byteArray, body...) + } + binary.BigEndian.PutUint32(byteArray[12:], uint32(pkgLen)) + return byteArray, nil +} + +func packResponse(p DubboPackage, serializer Serializer) ([]byte, error) { + var ( + byteArray []byte + ) + header := p.Header + hb := p.IsHeartBeat() + + // magic + if hb { + byteArray = append(byteArray, DubboResponseHeartbeatHeader[:]...) + } else { + byteArray = append(byteArray, DubboResponseHeaderBytes[:]...) + } + // set serialID, identify serialization types, eg: fastjson->6, hessian2->2 + byteArray[2] |= header.SerialID & SERIAL_MASK + // response status + if header.ResponseStatus != 0 { + byteArray[3] = header.ResponseStatus + } + + // request id + binary.BigEndian.PutUint64(byteArray[4:], uint64(header.ID)) + + // body + body, err := serializer.Marshal(p) + if err != nil { + return nil, err + } + + pkgLen := len(body) + if pkgLen > int(DEFAULT_LEN) { // 8M + return nil, perrors.Errorf("Data length %d too large, max payload %d", pkgLen, DEFAULT_LEN) + } + // byteArray{body length} + binary.BigEndian.PutUint32(byteArray[12:], uint32(pkgLen)) + byteArray = append(byteArray, body...) + return byteArray, nil +} + +func NewDubboCodec(reader *bufio.Reader) *ProtocolCodec { + s, _ := GetSerializerById(constant.S_Hessian2) + return &ProtocolCodec{ + reader: reader, + pkgType: 0, + bodyLen: 0, + headerRead: false, + serializer: s.(Serializer), + } +} diff --git a/protocol/dubbo/codec_test.go b/protocol/dubbo/impl/codec_test.go similarity index 50% rename from protocol/dubbo/codec_test.go rename to protocol/dubbo/impl/codec_test.go index 6dfb87ea00..1c37928238 100644 --- a/protocol/dubbo/codec_test.go +++ b/protocol/dubbo/impl/codec_test.go @@ -15,45 +15,46 @@ * limitations under the License. */ -package dubbo +package impl import ( - "bytes" "testing" "time" ) import ( - perrors "github.com/pkg/errors" "github.com/stretchr/testify/assert" ) import ( - "github.com/apache/dubbo-go/protocol/dubbo/hessian2" + "github.com/apache/dubbo-go/common/constant" ) -func TestDubboPackageMarshalAndUnmarshal(t *testing.T) { - pkg := &DubboPackage{} +func TestDubboPackage_MarshalAndUnmarshal(t *testing.T) { + pkg := NewDubboPackage(nil) pkg.Body = []interface{}{"a"} - pkg.Header.Type = hessian2.PackageHeartbeat - pkg.Header.SerialID = byte(S_Dubbo) + pkg.Header.Type = PackageHeartbeat + pkg.Header.SerialID = constant.S_Hessian2 pkg.Header.ID = 10086 + pkg.SetSerializer(HessianSerializer{}) // heartbeat data, err := pkg.Marshal() assert.NoError(t, err) - pkgres := &DubboPackage{} + pkgres := NewDubboPackage(data) + pkgres.SetSerializer(HessianSerializer{}) + pkgres.Body = []interface{}{} - err = pkgres.Unmarshal(data) + err = pkgres.Unmarshal() assert.NoError(t, err) - assert.Equal(t, hessian2.PackageHeartbeat|hessian2.PackageRequest|hessian2.PackageRequest_TwoWay, pkgres.Header.Type) - assert.Equal(t, byte(S_Dubbo), pkgres.Header.SerialID) + assert.Equal(t, PackageHeartbeat|PackageRequest|PackageRequest_TwoWay, pkgres.Header.Type) + assert.Equal(t, constant.S_Hessian2, pkgres.Header.SerialID) assert.Equal(t, int64(10086), pkgres.Header.ID) assert.Equal(t, 0, len(pkgres.Body.([]interface{}))) // request - pkg.Header.Type = hessian2.PackageRequest + pkg.Header.Type = PackageRequest pkg.Service.Interface = "Service" pkg.Service.Path = "path" pkg.Service.Version = "2.6" @@ -62,25 +63,27 @@ func TestDubboPackageMarshalAndUnmarshal(t *testing.T) { data, err = pkg.Marshal() assert.NoError(t, err) - pkgres = &DubboPackage{} + pkgres = NewDubboPackage(data) + pkgres.SetSerializer(HessianSerializer{}) pkgres.Body = make([]interface{}, 7) - err = pkgres.Unmarshal(data) + err = pkgres.Unmarshal() + reassembleBody := pkgres.GetBody().(map[string]interface{}) assert.NoError(t, err) - assert.Equal(t, hessian2.PackageRequest, pkgres.Header.Type) - assert.Equal(t, byte(S_Dubbo), pkgres.Header.SerialID) + assert.Equal(t, PackageRequest, pkgres.Header.Type) + assert.Equal(t, constant.S_Hessian2, pkgres.Header.SerialID) assert.Equal(t, int64(10086), pkgres.Header.ID) - assert.Equal(t, "2.0.2", pkgres.Body.([]interface{})[0]) - assert.Equal(t, "path", pkgres.Body.([]interface{})[1]) - assert.Equal(t, "2.6", pkgres.Body.([]interface{})[2]) - assert.Equal(t, "Method", pkgres.Body.([]interface{})[3]) - assert.Equal(t, "Ljava/lang/String;", pkgres.Body.([]interface{})[4]) - assert.Equal(t, []interface{}{"a"}, pkgres.Body.([]interface{})[5]) - assert.Equal(t, map[string]interface{}{"dubbo": "2.0.2", "interface": "Service", "path": "path", "timeout": "1000", "version": "2.6"}, pkgres.Body.([]interface{})[6]) -} - -func TestIssue380(t *testing.T) { - pkg := &DubboPackage{} - buf := bytes.NewBuffer([]byte("hello")) - err := pkg.Unmarshal(buf) - assert.True(t, perrors.Cause(err) == hessian2.ErrHeaderNotEnough) + assert.Equal(t, "2.0.2", reassembleBody["dubboVersion"].(string)) + assert.Equal(t, "path", pkgres.Service.Path) + assert.Equal(t, "2.6", pkgres.Service.Version) + assert.Equal(t, "Method", pkgres.Service.Method) + assert.Equal(t, "Ljava/lang/String;", reassembleBody["argsTypes"].(string)) + assert.Equal(t, []interface{}{"a"}, reassembleBody["args"]) + tmpData := map[string]interface{}{ + "dubbo": "2.0.2", + "interface": "Service", + "path": "path", + "timeout": "1000", + "version": "2.6", + } + assert.Equal(t, tmpData, reassembleBody["attachments"]) } diff --git a/protocol/dubbo/impl/const.go b/protocol/dubbo/impl/const.go new file mode 100644 index 0000000000..70d8bae6ca --- /dev/null +++ b/protocol/dubbo/impl/const.go @@ -0,0 +1,252 @@ +/* + * 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 impl + +import ( + "reflect" + "regexp" + + "github.com/pkg/errors" +) + +const ( + DUBBO = "dubbo" +) + +const ( + mask = byte(127) + flag = byte(128) +) + +const ( + // Zero : byte zero + Zero = byte(0x00) +) + +// constansts +const ( + TAG_READ = int32(-1) + ASCII_GAP = 32 + CHUNK_SIZE = 4096 + BC_BINARY = byte('B') // final chunk + BC_BINARY_CHUNK = byte('A') // non-final chunk + + BC_BINARY_DIRECT = byte(0x20) // 1-byte length binary + BINARY_DIRECT_MAX = byte(0x0f) + BC_BINARY_SHORT = byte(0x34) // 2-byte length binary + BINARY_SHORT_MAX = 0x3ff // 0-1023 binary + + BC_DATE = byte(0x4a) // 64-bit millisecond UTC date + BC_DATE_MINUTE = byte(0x4b) // 32-bit minute UTC date + + BC_DOUBLE = byte('D') // IEEE 64-bit double + + BC_DOUBLE_ZERO = byte(0x5b) + BC_DOUBLE_ONE = byte(0x5c) + BC_DOUBLE_BYTE = byte(0x5d) + BC_DOUBLE_SHORT = byte(0x5e) + BC_DOUBLE_MILL = byte(0x5f) + + BC_FALSE = byte('F') // boolean false + + BC_INT = byte('I') // 32-bit int + + INT_DIRECT_MIN = -0x10 + INT_DIRECT_MAX = byte(0x2f) + BC_INT_ZERO = byte(0x90) + + INT_BYTE_MIN = -0x800 + INT_BYTE_MAX = 0x7ff + BC_INT_BYTE_ZERO = byte(0xc8) + + BC_END = byte('Z') + + INT_SHORT_MIN = -0x40000 + INT_SHORT_MAX = 0x3ffff + BC_INT_SHORT_ZERO = byte(0xd4) + + BC_LIST_VARIABLE = byte(0x55) + BC_LIST_FIXED = byte('V') + BC_LIST_VARIABLE_UNTYPED = byte(0x57) + BC_LIST_FIXED_UNTYPED = byte(0x58) + _listFixedTypedLenTagMin = byte(0x70) + _listFixedTypedLenTagMax = byte(0x77) + _listFixedUntypedLenTagMin = byte(0x78) + _listFixedUntypedLenTagMax = byte(0x7f) + + BC_LIST_DIRECT = byte(0x70) + BC_LIST_DIRECT_UNTYPED = byte(0x78) + LIST_DIRECT_MAX = byte(0x7) + + BC_LONG = byte('L') // 64-bit signed integer + LONG_DIRECT_MIN = -0x08 + LONG_DIRECT_MAX = byte(0x0f) + BC_LONG_ZERO = byte(0xe0) + + LONG_BYTE_MIN = -0x800 + LONG_BYTE_MAX = 0x7ff + BC_LONG_BYTE_ZERO = byte(0xf8) + + LONG_SHORT_MIN = -0x40000 + LONG_SHORT_MAX = 0x3ffff + BC_LONG_SHORT_ZERO = byte(0x3c) + + BC_LONG_INT = byte(0x59) + + BC_MAP = byte('M') + BC_MAP_UNTYPED = byte('H') + + BC_NULL = byte('N') // x4e + + BC_OBJECT = byte('O') + BC_OBJECT_DEF = byte('C') + + BC_OBJECT_DIRECT = byte(0x60) + OBJECT_DIRECT_MAX = byte(0x0f) + + BC_REF = byte(0x51) + + BC_STRING = byte('S') // final string + BC_STRING_CHUNK = byte('R') // non-final string + + BC_STRING_DIRECT = byte(0x00) + STRING_DIRECT_MAX = byte(0x1f) + BC_STRING_SHORT = byte(0x30) + STRING_SHORT_MAX = 0x3ff + + BC_TRUE = byte('T') + + P_PACKET_CHUNK = byte(0x4f) + P_PACKET = byte('P') + + P_PACKET_DIRECT = byte(0x80) + PACKET_DIRECT_MAX = byte(0x7f) + + P_PACKET_SHORT = byte(0x70) + PACKET_SHORT_MAX = 0xfff + ARRAY_STRING = "[string" + ARRAY_INT = "[int" + ARRAY_DOUBLE = "[double" + ARRAY_FLOAT = "[float" + ARRAY_BOOL = "[boolean" + ARRAY_LONG = "[long" + + PATH_KEY = "path" + GROUP_KEY = "group" + INTERFACE_KEY = "interface" + VERSION_KEY = "version" + TIMEOUT_KEY = "timeout" + + STRING_NIL = "" + STRING_TRUE = "true" + STRING_FALSE = "false" + STRING_ZERO = "0.0" + STRING_ONE = "1.0" +) + +// ResponsePayload related consts +const ( + Response_OK byte = 20 + Response_CLIENT_TIMEOUT byte = 30 + Response_SERVER_TIMEOUT byte = 31 + Response_BAD_REQUEST byte = 40 + Response_BAD_RESPONSE byte = 50 + Response_SERVICE_NOT_FOUND byte = 60 + Response_SERVICE_ERROR byte = 70 + Response_SERVER_ERROR byte = 80 + Response_CLIENT_ERROR byte = 90 + + // According to "java dubbo" There are two cases of response: + // 1. with attachments + // 2. no attachments + RESPONSE_WITH_EXCEPTION int32 = 0 + RESPONSE_VALUE int32 = 1 + RESPONSE_NULL_VALUE int32 = 2 + RESPONSE_WITH_EXCEPTION_WITH_ATTACHMENTS int32 = 3 + RESPONSE_VALUE_WITH_ATTACHMENTS int32 = 4 + RESPONSE_NULL_VALUE_WITH_ATTACHMENTS int32 = 5 +) + +/** + * the dubbo protocol header length is 16 Bytes. + * the first 2 Bytes is magic code '0xdabb' + * the next 1 Byte is message flags, in which its 16-20 bit is serial id, 21 for event, 22 for two way, 23 for request/response flag + * the next 1 Bytes is response state. + * the next 8 Bytes is package DI. + * the next 4 Bytes is package length. + **/ +const ( + // header length. + HEADER_LENGTH = 16 + + // magic header + MAGIC = uint16(0xdabb) + MAGIC_HIGH = byte(0xda) + MAGIC_LOW = byte(0xbb) + + // message flag. + FLAG_REQUEST = byte(0x80) + FLAG_TWOWAY = byte(0x40) + FLAG_EVENT = byte(0x20) // for heartbeat + SERIAL_MASK = 0x1f + + DUBBO_VERSION = "2.5.4" + DUBBO_VERSION_KEY = "dubbo" + DEFAULT_DUBBO_PROTOCOL_VERSION = "2.0.2" // Dubbo RPC protocol version, for compatibility, it must not be between 2.0.10 ~ 2.6.2 + LOWEST_VERSION_FOR_RESPONSE_ATTACHMENT = 2000200 + DEFAULT_LEN = 8388608 // 8 * 1024 * 1024 default body max length +) + +// regular +const ( + JAVA_IDENT_REGEX = "(?:[_$a-zA-Z][_$a-zA-Z0-9]*)" + CLASS_DESC = "(?:L" + JAVA_IDENT_REGEX + "(?:\\/" + JAVA_IDENT_REGEX + ")*;)" + ARRAY_DESC = "(?:\\[+(?:(?:[VZBCDFIJS])|" + CLASS_DESC + "))" + DESC_REGEX = "(?:(?:[VZBCDFIJS])|" + CLASS_DESC + "|" + ARRAY_DESC + ")" +) + +// Dubbo request response related consts +var ( + DubboRequestHeaderBytesTwoWay = [HEADER_LENGTH]byte{MAGIC_HIGH, MAGIC_LOW, FLAG_REQUEST | FLAG_TWOWAY} + DubboRequestHeaderBytes = [HEADER_LENGTH]byte{MAGIC_HIGH, MAGIC_LOW, FLAG_REQUEST} + DubboResponseHeaderBytes = [HEADER_LENGTH]byte{MAGIC_HIGH, MAGIC_LOW, Zero, Response_OK} + DubboRequestHeartbeatHeader = [HEADER_LENGTH]byte{MAGIC_HIGH, MAGIC_LOW, FLAG_REQUEST | FLAG_TWOWAY | FLAG_EVENT} + DubboResponseHeartbeatHeader = [HEADER_LENGTH]byte{MAGIC_HIGH, MAGIC_LOW, FLAG_EVENT} +) + +// Error part +var ( + ErrHeaderNotEnough = errors.New("header buffer too short") + ErrBodyNotEnough = errors.New("body buffer too short") + ErrJavaException = errors.New("got java exception") + ErrIllegalPackage = errors.New("illegal package!") +) + +// DescRegex ... +var DescRegex, _ = regexp.Compile(DESC_REGEX) + +var NilValue = reflect.Zero(reflect.TypeOf((*interface{})(nil)).Elem()) + +// Body map keys +var ( + DubboVersionKey = "dubboVersion" + ArgsTypesKey = "argsTypes" + ArgsKey = "args" + ServiceKey = "service" + AttachmentsKey = "attachments" +) diff --git a/protocol/dubbo/impl/hessian.go b/protocol/dubbo/impl/hessian.go new file mode 100644 index 0000000000..b686d5728d --- /dev/null +++ b/protocol/dubbo/impl/hessian.go @@ -0,0 +1,526 @@ +/* + * 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 impl + +import ( + "math" + "reflect" + "strconv" + "strings" + "time" +) + +import ( + hessian "github.com/apache/dubbo-go-hessian2" + "github.com/apache/dubbo-go-hessian2/java_exception" + perrors "github.com/pkg/errors" +) + +import ( + "github.com/apache/dubbo-go/common" + "github.com/apache/dubbo-go/common/constant" + "github.com/apache/dubbo-go/common/logger" +) + +type Object interface{} + +type HessianSerializer struct { +} + +func (h HessianSerializer) Marshal(p DubboPackage) ([]byte, error) { + encoder := hessian.NewEncoder() + if p.IsRequest() { + return marshalRequest(encoder, p) + } + return marshalResponse(encoder, p) +} + +func (h HessianSerializer) Unmarshal(input []byte, p *DubboPackage) error { + if p.IsHeartBeat() { + return nil + } + if p.IsRequest() { + return unmarshalRequestBody(input, p) + } + return unmarshalResponseBody(input, p) +} + +func marshalResponse(encoder *hessian.Encoder, p DubboPackage) ([]byte, error) { + header := p.Header + response := EnsureResponsePayload(p.Body) + if header.ResponseStatus == Response_OK { + if p.IsHeartBeat() { + encoder.Encode(nil) + } else { + var version string + if attachmentVersion, ok := response.Attachments[DUBBO_VERSION_KEY]; ok { + version = attachmentVersion.(string) + } + atta := isSupportResponseAttachment(version) + + var resWithException, resValue, resNullValue int32 + if atta { + resWithException = RESPONSE_WITH_EXCEPTION_WITH_ATTACHMENTS + resValue = RESPONSE_VALUE_WITH_ATTACHMENTS + resNullValue = RESPONSE_NULL_VALUE_WITH_ATTACHMENTS + } else { + resWithException = RESPONSE_WITH_EXCEPTION + resValue = RESPONSE_VALUE + resNullValue = RESPONSE_NULL_VALUE + } + + if response.Exception != nil { // throw error + encoder.Encode(resWithException) + if t, ok := response.Exception.(java_exception.Throwabler); ok { + encoder.Encode(t) + } else { + encoder.Encode(java_exception.NewThrowable(response.Exception.Error())) + } + } else { + if response.RspObj == nil { + encoder.Encode(resNullValue) + } else { + encoder.Encode(resValue) + encoder.Encode(response.RspObj) // result + } + } + + if atta { + encoder.Encode(response.Attachments) // attachments + } + } + } else { + if response.Exception != nil { // throw error + encoder.Encode(response.Exception.Error()) + } else { + encoder.Encode(response.RspObj) + } + } + bs := encoder.Buffer() + // encNull + bs = append(bs, byte('N')) + return bs, nil +} + +func marshalRequest(encoder *hessian.Encoder, p DubboPackage) ([]byte, error) { + service := p.Service + request := EnsureRequestPayload(p.Body) + encoder.Encode(DEFAULT_DUBBO_PROTOCOL_VERSION) + encoder.Encode(service.Path) + encoder.Encode(service.Version) + encoder.Encode(service.Method) + + args, ok := request.Params.([]interface{}) + + if !ok { + logger.Infof("request args are: %+v", request.Params) + return nil, perrors.Errorf("@params is not of type: []interface{}") + } + types, err := getArgsTypeList(args) + if err != nil { + return nil, perrors.Wrapf(err, " PackRequest(args:%+v)", args) + } + encoder.Encode(types) + for _, v := range args { + encoder.Encode(v) + } + + request.Attachments[PATH_KEY] = service.Path + request.Attachments[VERSION_KEY] = service.Version + if len(service.Group) > 0 { + request.Attachments[GROUP_KEY] = service.Group + } + if len(service.Interface) > 0 { + request.Attachments[INTERFACE_KEY] = service.Interface + } + if service.Timeout != 0 { + request.Attachments[TIMEOUT_KEY] = strconv.Itoa(int(service.Timeout / time.Millisecond)) + } + + encoder.Encode(request.Attachments) + return encoder.Buffer(), nil + +} + +var versionInt = make(map[string]int) + +// https://github.com/apache/dubbo/blob/dubbo-2.7.1/dubbo-common/src/main/java/org/apache/dubbo/common/Version.java#L96 +// isSupportResponseAttachment is for compatibility among some dubbo version +func isSupportResponseAttachment(version string) bool { + if version == "" { + return false + } + + v, ok := versionInt[version] + if !ok { + v = version2Int(version) + if v == -1 { + return false + } + } + + if v >= 2001000 && v <= 2060200 { // 2.0.10 ~ 2.6.2 + return false + } + return v >= LOWEST_VERSION_FOR_RESPONSE_ATTACHMENT +} + +func version2Int(version string) int { + var v = 0 + varr := strings.Split(version, ".") + length := len(varr) + for key, value := range varr { + v0, err := strconv.Atoi(value) + if err != nil { + return -1 + } + v += v0 * int(math.Pow10((length-key-1)*2)) + } + if length == 3 { + return v * 100 + } + return v +} + +func unmarshalRequestBody(body []byte, p *DubboPackage) error { + if p.Body == nil { + p.SetBody(make([]interface{}, 7)) + } + decoder := hessian.NewDecoder(body) + var ( + err error + dubboVersion, target, serviceVersion, method, argsTypes interface{} + args []interface{} + ) + req, ok := p.Body.([]interface{}) + if !ok { + return perrors.Errorf("@reqObj is not of type: []interface{}") + } + dubboVersion, err = decoder.Decode() + if err != nil { + return perrors.WithStack(err) + } + req[0] = dubboVersion + + target, err = decoder.Decode() + if err != nil { + return perrors.WithStack(err) + } + req[1] = target + + serviceVersion, err = decoder.Decode() + if err != nil { + return perrors.WithStack(err) + } + req[2] = serviceVersion + + method, err = decoder.Decode() + if err != nil { + return perrors.WithStack(err) + } + req[3] = method + + argsTypes, err = decoder.Decode() + if err != nil { + return perrors.WithStack(err) + } + req[4] = argsTypes + + ats := hessian.DescRegex.FindAllString(argsTypes.(string), -1) + var arg interface{} + for i := 0; i < len(ats); i++ { + arg, err = decoder.Decode() + if err != nil { + return perrors.WithStack(err) + } + args = append(args, arg) + } + req[5] = args + + attachments, err := decoder.Decode() + if err != nil { + return perrors.WithStack(err) + } + + if v, ok := attachments.(map[interface{}]interface{}); ok { + v[DUBBO_VERSION_KEY] = dubboVersion + req[6] = ToMapStringInterface(v) + buildServerSidePackageBody(p) + return nil + } + return perrors.Errorf("get wrong attachments: %+v", attachments) +} + +func unmarshalResponseBody(body []byte, p *DubboPackage) error { + decoder := hessian.NewDecoder(body) + rspType, err := decoder.Decode() + if p.Body == nil { + p.SetBody(&ResponsePayload{}) + } + if err != nil { + return perrors.WithStack(err) + } + response := EnsureResponsePayload(p.Body) + + switch rspType { + case RESPONSE_WITH_EXCEPTION, RESPONSE_WITH_EXCEPTION_WITH_ATTACHMENTS: + expt, err := decoder.Decode() + if err != nil { + return perrors.WithStack(err) + } + if rspType == RESPONSE_WITH_EXCEPTION_WITH_ATTACHMENTS { + attachments, err := decoder.Decode() + if err != nil { + return perrors.WithStack(err) + } + if v, ok := attachments.(map[interface{}]interface{}); ok { + atta := ToMapStringInterface(v) + response.Attachments = atta + } else { + return perrors.Errorf("get wrong attachments: %+v", attachments) + } + } + + if e, ok := expt.(error); ok { + response.Exception = e + } else { + response.Exception = perrors.Errorf("got exception: %+v", expt) + } + return nil + + case RESPONSE_VALUE, RESPONSE_VALUE_WITH_ATTACHMENTS: + rsp, err := decoder.Decode() + if err != nil { + return perrors.WithStack(err) + } + if rspType == RESPONSE_VALUE_WITH_ATTACHMENTS { + attachments, err := decoder.Decode() + if err != nil { + return perrors.WithStack(err) + } + if v, ok := attachments.(map[interface{}]interface{}); ok { + atta := ToMapStringInterface(v) + response.Attachments = atta + } else { + return perrors.Errorf("get wrong attachments: %+v", attachments) + } + } + + return perrors.WithStack(hessian.ReflectResponse(rsp, response.RspObj)) + + case RESPONSE_NULL_VALUE, RESPONSE_NULL_VALUE_WITH_ATTACHMENTS: + if rspType == RESPONSE_NULL_VALUE_WITH_ATTACHMENTS { + attachments, err := decoder.Decode() + if err != nil { + return perrors.WithStack(err) + } + if v, ok := attachments.(map[interface{}]interface{}); ok { + atta := ToMapStringInterface(v) + response.Attachments = atta + } else { + return perrors.Errorf("get wrong attachments: %+v", attachments) + } + } + return nil + } + return nil +} + +func buildServerSidePackageBody(pkg *DubboPackage) { + req := pkg.GetBody().([]interface{}) // length of body should be 7 + if len(req) > 0 { + var dubboVersion, argsTypes string + var args []interface{} + var attachments map[string]interface{} + svc := Service{} + if req[0] != nil { + dubboVersion = req[0].(string) + } + if req[1] != nil { + svc.Path = req[1].(string) + } + if req[2] != nil { + svc.Version = req[2].(string) + } + if req[3] != nil { + svc.Method = req[3].(string) + } + if req[4] != nil { + argsTypes = req[4].(string) + } + if req[5] != nil { + args = req[5].([]interface{}) + } + if req[6] != nil { + attachments = req[6].(map[string]interface{}) + } + if svc.Path == "" && attachments[constant.PATH_KEY] != nil && len(attachments[constant.PATH_KEY].(string)) > 0 { + svc.Path = attachments[constant.PATH_KEY].(string) + } + if _, ok := attachments[constant.INTERFACE_KEY]; ok { + svc.Interface = attachments[constant.INTERFACE_KEY].(string) + } else { + svc.Interface = svc.Path + } + if _, ok := attachments[constant.GROUP_KEY]; ok { + svc.Group = attachments[constant.GROUP_KEY].(string) + } + pkg.SetService(svc) + pkg.SetBody(map[string]interface{}{ + "dubboVersion": dubboVersion, + "argsTypes": argsTypes, + "args": args, + "service": common.ServiceMap.GetService(DUBBO, svc.Path), // path as a key + "attachments": attachments, + }) + } +} + +func getArgsTypeList(args []interface{}) (string, error) { + var ( + typ string + types string + ) + + for i := range args { + typ = getArgType(args[i]) + if typ == "" { + return types, perrors.Errorf("cat not get arg %#v type", args[i]) + } + if !strings.Contains(typ, ".") { + types += typ + } else if strings.Index(typ, "[") == 0 { + types += strings.Replace(typ, ".", "/", -1) + } else { + // java.util.List -> Ljava/util/List; + types += "L" + strings.Replace(typ, ".", "/", -1) + ";" + } + } + + return types, nil +} + +func getArgType(v interface{}) string { + if v == nil { + return "V" + } + + switch v.(type) { + // Serialized tags for base types + case nil: + return "V" + case bool: + return "Z" + case []bool: + return "[Z" + case byte: + return "B" + case []byte: + return "[B" + case int8: + return "B" + case []int8: + return "[B" + case int16: + return "S" + case []int16: + return "[S" + case uint16: // Equivalent to Char of Java + return "C" + case []uint16: + return "[C" + // case rune: + // return "C" + case int: + return "J" + case []int: + return "[J" + case int32: + return "I" + case []int32: + return "[I" + case int64: + return "J" + case []int64: + return "[J" + case time.Time: + return "java.util.Date" + case []time.Time: + return "[Ljava.util.Date" + case float32: + return "F" + case []float32: + return "[F" + case float64: + return "D" + case []float64: + return "[D" + case string: + return "java.lang.String" + case []string: + return "[Ljava.lang.String;" + case []Object: + return "[Ljava.lang.Object;" + case map[interface{}]interface{}: + // return "java.util.HashMap" + return "java.util.Map" + case hessian.POJOEnum: + return v.(hessian.POJOEnum).JavaClassName() + // Serialized tags for complex types + default: + t := reflect.TypeOf(v) + if reflect.Ptr == t.Kind() { + t = reflect.TypeOf(reflect.ValueOf(v).Elem()) + } + switch t.Kind() { + case reflect.Struct: + return "java.lang.Object" + case reflect.Slice, reflect.Array: + if t.Elem().Kind() == reflect.Struct { + return "[Ljava.lang.Object;" + } + // return "java.util.ArrayList" + return "java.util.List" + case reflect.Map: // Enter here, map may be map[string]int + return "java.util.Map" + default: + return "" + } + } + + // unreachable + // return "java.lang.RuntimeException" +} + +func ToMapStringInterface(origin map[interface{}]interface{}) map[string]interface{} { + dest := make(map[string]interface{}, len(origin)) + for k, v := range origin { + if kv, ok := k.(string); ok { + if v == nil { + dest[kv] = "" + continue + } + dest[kv] = v + } + } + return dest +} + +func init() { + SetSerializer("hessian2", HessianSerializer{}) +} diff --git a/protocol/dubbo/impl/package.go b/protocol/dubbo/impl/package.go new file mode 100644 index 0000000000..6f6d2ea975 --- /dev/null +++ b/protocol/dubbo/impl/package.go @@ -0,0 +1,171 @@ +/* + * 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 impl + +import ( + "bufio" + "bytes" + "fmt" + "time" +) + +import ( + "github.com/pkg/errors" +) + +type PackageType int + +// enum part +const ( + PackageError = PackageType(0x01) + PackageRequest = PackageType(0x02) + PackageResponse = PackageType(0x04) + PackageHeartbeat = PackageType(0x08) + PackageRequest_TwoWay = PackageType(0x10) + PackageResponse_Exception = PackageType(0x20) + PackageType_BitSize = 0x2f +) + +type DubboHeader struct { + SerialID byte + Type PackageType + ID int64 + BodyLen int + ResponseStatus byte +} + +// Service defines service instance +type Service struct { + Path string + Interface string + Group string + Version string + Method string + Timeout time.Duration // request timeout +} + +type DubboPackage struct { + Header DubboHeader + Service Service + Body interface{} + Err error + Codec *ProtocolCodec +} + +func (p DubboPackage) String() string { + return fmt.Sprintf("HessianPackage: Header-%v, Path-%v, Body-%v", p.Header, p.Service, p.Body) +} + +func (p *DubboPackage) ReadHeader() error { + return p.Codec.ReadHeader(&p.Header) +} + +func (p *DubboPackage) Marshal() (*bytes.Buffer, error) { + if p.Codec == nil { + return nil, errors.New("Codec is nil") + } + pkg, err := p.Codec.Encode(*p) + if err != nil { + return nil, errors.WithStack(err) + } + return bytes.NewBuffer(pkg), nil +} + +func (p *DubboPackage) Unmarshal() error { + if p.Codec == nil { + return errors.New("Codec is nil") + } + return p.Codec.Decode(p) +} + +func (p DubboPackage) IsHeartBeat() bool { + return p.Header.Type&PackageHeartbeat != 0 +} + +func (p DubboPackage) IsRequest() bool { + return p.Header.Type&(PackageRequest_TwoWay|PackageRequest) != 0 +} + +func (p DubboPackage) IsResponse() bool { + return p.Header.Type == PackageResponse +} + +func (p DubboPackage) IsResponseWithException() bool { + flag := PackageResponse | PackageResponse_Exception + return p.Header.Type&flag == flag +} + +func (p DubboPackage) GetBodyLen() int { + return p.Header.BodyLen +} + +func (p DubboPackage) GetLen() int { + return HEADER_LENGTH + p.Header.BodyLen +} + +func (p DubboPackage) GetBody() interface{} { + return p.Body +} + +func (p *DubboPackage) SetBody(body interface{}) { + p.Body = body +} + +func (p *DubboPackage) SetHeader(header DubboHeader) { + p.Header = header +} + +func (p *DubboPackage) SetService(svc Service) { + p.Service = svc +} + +func (p *DubboPackage) SetID(id int64) { + p.Header.ID = id +} + +func (p DubboPackage) GetHeader() DubboHeader { + return p.Header +} + +func (p DubboPackage) GetService() Service { + return p.Service +} + +func (p *DubboPackage) SetResponseStatus(status byte) { + p.Header.ResponseStatus = status +} + +func (p *DubboPackage) SetSerializer(serializer Serializer) { + p.Codec.SetSerializer(serializer) +} + +func NewDubboPackage(data *bytes.Buffer) *DubboPackage { + var codec *ProtocolCodec + if data == nil { + codec = NewDubboCodec(nil) + } else { + codec = NewDubboCodec(bufio.NewReaderSize(data, len(data.Bytes()))) + } + return &DubboPackage{ + Header: DubboHeader{}, + Service: Service{}, + Body: nil, + Err: nil, + Codec: codec, + } +} diff --git a/protocol/dubbo/impl/request.go b/protocol/dubbo/impl/request.go new file mode 100644 index 0000000000..ef520083e6 --- /dev/null +++ b/protocol/dubbo/impl/request.go @@ -0,0 +1,40 @@ +/* + * 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 impl + +type RequestPayload struct { + Params interface{} + Attachments map[string]interface{} +} + +func NewRequestPayload(args interface{}, atta map[string]interface{}) *RequestPayload { + if atta == nil { + atta = make(map[string]interface{}) + } + return &RequestPayload{ + Params: args, + Attachments: atta, + } +} + +func EnsureRequestPayload(body interface{}) *RequestPayload { + if req, ok := body.(*RequestPayload); ok { + return req + } + return NewRequestPayload(body, nil) +} diff --git a/protocol/dubbo/impl/response.go b/protocol/dubbo/impl/response.go new file mode 100644 index 0000000000..9fde1eb249 --- /dev/null +++ b/protocol/dubbo/impl/response.go @@ -0,0 +1,46 @@ +/* + * 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 impl + +type ResponsePayload struct { + RspObj interface{} + Exception error + Attachments map[string]interface{} +} + +// NewResponse create a new ResponsePayload +func NewResponsePayload(rspObj interface{}, exception error, attachments map[string]interface{}) *ResponsePayload { + if attachments == nil { + attachments = make(map[string]interface{}) + } + return &ResponsePayload{ + RspObj: rspObj, + Exception: exception, + Attachments: attachments, + } +} + +func EnsureResponsePayload(body interface{}) *ResponsePayload { + if res, ok := body.(*ResponsePayload); ok { + return res + } + if exp, ok := body.(error); ok { + return NewResponsePayload(nil, exp, nil) + } + return NewResponsePayload(body, nil, nil) +} diff --git a/protocol/dubbo/impl/serialization.go b/protocol/dubbo/impl/serialization.go new file mode 100644 index 0000000000..7ce76a87c1 --- /dev/null +++ b/protocol/dubbo/impl/serialization.go @@ -0,0 +1,54 @@ +/* + * 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 impl + +import ( + "fmt" +) + +import ( + "github.com/apache/dubbo-go/common/constant" +) + +var ( + serializers = make(map[string]interface{}) + nameMaps = make(map[byte]string) +) + +func init() { + nameMaps = map[byte]string{ + constant.S_Hessian2: constant.HESSIAN2_SERIALIZATION, + constant.S_Proto: constant.PROTOBUF_SERIALIZATION, + } +} + +func SetSerializer(name string, serializer interface{}) { + serializers[name] = serializer +} + +func GetSerializerById(id byte) (interface{}, error) { + name, ok := nameMaps[id] + if !ok { + panic(fmt.Sprintf("serialId %d not found", id)) + } + serializer, ok := serializers[name] + if !ok { + panic(fmt.Sprintf("serialization %s not found", name)) + } + return serializer, nil +} diff --git a/protocol/dubbo/impl/serialize.go b/protocol/dubbo/impl/serialize.go new file mode 100644 index 0000000000..1f913f7caa --- /dev/null +++ b/protocol/dubbo/impl/serialize.go @@ -0,0 +1,40 @@ +/* + * 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 impl + +import ( + "github.com/apache/dubbo-go/common/constant" +) + +type Serializer interface { + Marshal(p DubboPackage) ([]byte, error) + Unmarshal([]byte, *DubboPackage) error +} + +func LoadSerializer(p *DubboPackage) error { + // NOTE: default serialID is S_Hessian + serialID := p.Header.SerialID + if serialID == 0 { + serialID = constant.S_Hessian2 + } + serializer, err := GetSerializerById(serialID) + if err != nil { + panic(err) + } + p.SetSerializer(serializer.(Serializer)) + return nil +} diff --git a/protocol/dubbo/listener.go b/protocol/dubbo/listener.go deleted file mode 100644 index 180fd176f9..0000000000 --- a/protocol/dubbo/listener.go +++ /dev/null @@ -1,368 +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" - "sync" - "sync/atomic" - "time" -) - -import ( - "github.com/apache/dubbo-getty" - perrors "github.com/pkg/errors" -) - -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" - "github.com/apache/dubbo-go/protocol/dubbo/hessian2" - "github.com/apache/dubbo-go/protocol/invocation" -) - -// todo: writePkg_Timeout will entry *.yml -const ( - writePkg_Timeout = 5 * time.Second -) - -var ( - errTooManySessions = perrors.New("too many sessions") -) - -type rpcSession struct { - session getty.Session - 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) -} - -// ////////////////////////////////////////// -// RpcClientHandler -// ////////////////////////////////////////// - -// RpcClientHandler is handler of RPC Client -type RpcClientHandler struct { - conn *gettyRPCClient -} - -// NewRpcClientHandler creates RpcClientHandler with @gettyRPCClient -func NewRpcClientHandler(client *gettyRPCClient) *RpcClientHandler { - return &RpcClientHandler{conn: client} -} - -// OnOpen notified when RPC client session opened -func (h *RpcClientHandler) OnOpen(session getty.Session) error { - h.conn.addSession(session) - return nil -} - -// OnError notified when RPC client session got any error -func (h *RpcClientHandler) OnError(session getty.Session, err error) { - logger.Warnf("session{%s} got error{%v}, will be closed.", session.Stat(), err) - h.conn.removeSession(session) -} - -// OnOpen notified when RPC client session closed -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 -func (h *RpcClientHandler) OnMessage(session getty.Session, pkg interface{}) { - p, ok := pkg.(*DubboPackage) - if !ok { - logger.Errorf("illegal package") - return - } - - if p.Header.Type&hessian2.PackageHeartbeat != 0x00 { - if p.Header.Type&hessian2.PackageResponse != 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) - } - h.conn.pool.rpcClient.removePendingResponse(SequenceType(p.Header.ID)) - } else { - logger.Debugf("get rpc heartbeat request{header: %#v, service: %#v, body: %#v}", p.Header, p.Service, p.Body) - p.Header.ResponseStatus = hessian2.Response_OK - reply(session, p, hessian2.PackageHeartbeat) - } - return - } - logger.Debugf("get rpc response{header: %#v, body: %#v}", p.Header, p.Body) - - 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 - } - - if p.Err != nil { - pendingResponse.err = p.Err - } - - pendingResponse.response.atta = p.Body.(*Response).atta - - if pendingResponse.callback == nil { - pendingResponse.done <- struct{}{} - } else { - pendingResponse.callback(pendingResponse.GetCallResponse()) - } -} - -// OnCron notified when RPC client session got any message in cron job -func (h *RpcClientHandler) OnCron(session getty.Session) { - clientRpcSession, err := h.conn.getClientRpcSession(session) - if err != nil { - logger.Errorf("client.getClientSession(session{%s}) = error{%v}", - session.Stat(), perrors.WithStack(err)) - return - } - 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(), clientRpcSession.reqNum) - h.conn.removeSession(session) // -> h.conn.close() -> h.conn.pool.remove(h.conn) - return - } - - h.conn.pool.rpcClient.heartbeat(session) -} - -// ////////////////////////////////////////// -// RpcServerHandler -// ////////////////////////////////////////// - -// RpcServerHandler is handler of RPC Server -type RpcServerHandler struct { - maxSessionNum int - sessionTimeout time.Duration - sessionMap map[getty.Session]*rpcSession - rwlock sync.RWMutex -} - -// NewRpcServerHandler creates RpcServerHandler with @maxSessionNum and @sessionTimeout -func NewRpcServerHandler(maxSessionNum int, sessionTimeout time.Duration) *RpcServerHandler { - return &RpcServerHandler{ - maxSessionNum: maxSessionNum, - sessionTimeout: sessionTimeout, - sessionMap: make(map[getty.Session]*rpcSession), - } -} - -// OnOpen notified when RPC server session opened -func (h *RpcServerHandler) OnOpen(session getty.Session) error { - var err error - h.rwlock.RLock() - if h.maxSessionNum <= len(h.sessionMap) { - err = errTooManySessions - } - h.rwlock.RUnlock() - if err != nil { - return perrors.WithStack(err) - } - - logger.Infof("got session:%s", session.Stat()) - h.rwlock.Lock() - h.sessionMap[session] = &rpcSession{session: session} - h.rwlock.Unlock() - return nil -} - -// OnError notified when RPC server session got any error -func (h *RpcServerHandler) OnError(session getty.Session, err error) { - logger.Warnf("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 -func (h *RpcServerHandler) OnClose(session getty.Session) { - logger.Infof("session{%s} is closing......", session.Stat()) - h.rwlock.Lock() - delete(h.sessionMap, session) - h.rwlock.Unlock() -} - -// OnMessage notified when RPC server session got any message in connection -func (h *RpcServerHandler) OnMessage(session getty.Session, pkg interface{}) { - h.rwlock.Lock() - if _, ok := h.sessionMap[session]; ok { - h.sessionMap[session].reqNum++ - } - h.rwlock.Unlock() - - p, ok := pkg.(*DubboPackage) - if !ok { - logger.Errorf("illegal package{%#v}", pkg) - return - } - p.Header.ResponseStatus = hessian2.Response_OK - - // heartbeat - if p.Header.Type&hessian2.PackageHeartbeat != 0x00 { - logger.Debugf("get rpc heartbeat request{header: %#v, service: %#v, body: %#v}", p.Header, p.Service, p.Body) - reply(session, p, hessian2.PackageHeartbeat) - return - } - - twoway := true - // not twoway - if p.Header.Type&hessian2.PackageRequest_TwoWay == 0x00 { - twoway = false - } - - defer func() { - if e := recover(); e != nil { - p.Header.ResponseStatus = hessian2.Response_SERVER_ERROR - if err, ok := e.(error); ok { - logger.Errorf("OnMessage panic: %+v", perrors.WithStack(err)) - p.Body = perrors.WithStack(err) - } else if err, ok := e.(string); ok { - logger.Errorf("OnMessage panic: %+v", perrors.New(err)) - p.Body = perrors.New(err) - } else { - logger.Errorf("OnMessage panic: %+v, this is impossible.", e) - p.Body = e - } - - if !twoway { - return - } - reply(session, p, hessian2.PackageResponse) - } - - }() - - u := common.NewURLWithOptions(common.WithPath(p.Service.Path), common.WithParams(url.Values{}), - common.WithParamsValue(constant.GROUP_KEY, p.Service.Group), - common.WithParamsValue(constant.INTERFACE_KEY, p.Service.Interface), - common.WithParamsValue(constant.VERSION_KEY, p.Service.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.Header.ResponseStatus = hessian2.Response_OK - p.Body = err - reply(session, p, hessian2.PackageResponse) - return - } - invoker := exporter.(protocol.Exporter).GetInvoker() - if invoker != nil { - attachments := p.Body.(map[string]interface{})["attachments"].(map[string]interface{}) - attachments[constant.LOCAL_ADDR] = session.LocalAddr() - attachments[constant.REMOTE_ADDR] = session.RemoteAddr() - - args := p.Body.(map[string]interface{})["args"].([]interface{}) - inv := invocation.NewRPCInvocation(p.Service.Method, args, attachments) - - ctx := rebuildCtx(inv) - - result := invoker.Invoke(ctx, inv) - if err := result.Error(); err != nil { - p.Header.ResponseStatus = hessian2.Response_OK - p.Body = hessian2.NewResponse(nil, err, result.Attachments()) - } else { - res := result.Result() - p.Header.ResponseStatus = hessian2.Response_OK - p.Body = hessian2.NewResponse(res, nil, result.Attachments()) - } - } - - if !twoway { - return - } - reply(session, p, hessian2.PackageResponse) -} - -// OnCron notified when RPC server session got any message in cron job -func (h *RpcServerHandler) OnCron(session getty.Session) { - var ( - flag bool - active time.Time - ) - - h.rwlock.RLock() - if _, ok := h.sessionMap[session]; ok { - active = session.GetActive() - if h.sessionTimeout.Nanoseconds() < time.Since(active).Nanoseconds() { - flag = true - logger.Warnf("session{%s} timeout{%s}, reqNum{%d}", - session.Stat(), time.Since(active).String(), h.sessionMap[session].reqNum) - } - } - h.rwlock.RUnlock() - - if flag { - h.rwlock.Lock() - delete(h.sessionMap, session) - h.rwlock.Unlock() - session.Close() - } -} - -// 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 := extractTraceCtx(inv) - if err == nil { - ctx = context.WithValue(ctx, constant.TRACING_REMOTE_SPAN_CTX, spanCtx) - } - return ctx -} - -func reply(session getty.Session, req *DubboPackage, tp hessian2.PackageType) { - resp := &DubboPackage{ - Header: hessian2.DubboHeader{ - SerialID: req.Header.SerialID, - Type: tp, - ID: req.Header.ID, - ResponseStatus: req.Header.ResponseStatus, - }, - } - - if req.Header.Type&hessian2.PackageRequest != 0x00 { - resp.Body = req.Body - } else { - resp.Body = nil - } - - if err := session.WritePkg(resp, writePkg_Timeout); err != nil { - logger.Errorf("WritePkg error: %#v, %#v", perrors.WithStack(err), req.Header) - } -} diff --git a/protocol/dubbo/readwriter.go b/protocol/dubbo/readwriter.go deleted file mode 100644 index a7b37aae76..0000000000 --- a/protocol/dubbo/readwriter.go +++ /dev/null @@ -1,190 +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" - "reflect" -) - -import ( - "github.com/apache/dubbo-getty" - perrors "github.com/pkg/errors" -) - -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/hessian2" -) - -//////////////////////////////////////////// -// RpcClientPackageHandler -//////////////////////////////////////////// - -// RpcClientPackageHandler handle package for client in getty. -type RpcClientPackageHandler struct { - client *Client -} - -// NewRpcClientPackageHandler create a RpcClientPackageHandler. -func NewRpcClientPackageHandler(client *Client) *RpcClientPackageHandler { - return &RpcClientPackageHandler{client: client} -} - -// Read decode @data to DubboPackage. -func (p *RpcClientPackageHandler) Read(ss getty.Session, data []byte) (interface{}, int, error) { - pkg := &DubboPackage{} - - buf := bytes.NewBuffer(data) - err := pkg.Unmarshal(buf, p.client) - if err != nil { - originErr := perrors.Cause(err) - if originErr == hessian2.ErrHeaderNotEnough || originErr == hessian2.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) - } - - if pkg.Header.Type&hessian2.PackageRequest == 0x00 { - pkg.Err = pkg.Body.(*hessian2.DubboResponse).Exception - pkg.Body = NewResponse(pkg.Body.(*hessian2.DubboResponse).RspObj, pkg.Body.(*hessian2.DubboResponse).Attachments) - } - - return pkg, hessian2.HEADER_LENGTH + pkg.Header.BodyLen, nil -} - -// Write encode @pkg. -func (p *RpcClientPackageHandler) Write(ss getty.Session, pkg interface{}) ([]byte, error) { - req, ok := pkg.(*DubboPackage) - if !ok { - logger.Errorf("illegal pkg:%+v\n", pkg) - return nil, perrors.New("invalid rpc request") - } - - buf, err := req.Marshal() - if err != nil { - logger.Warnf("binary.Write(req{%#v}) = err{%#v}", req, perrors.WithStack(err)) - return nil, perrors.WithStack(err) - } - - return buf.Bytes(), nil -} - -//////////////////////////////////////////// -// RpcServerPackageHandler -//////////////////////////////////////////// - -var ( - rpcServerPkgHandler = &RpcServerPackageHandler{} -) - -// RpcServerPackageHandler handle package for server in getty. -type RpcServerPackageHandler struct{} - -// Read decode @data to DubboPackage. -func (p *RpcServerPackageHandler) Read(ss getty.Session, data []byte) (interface{}, int, error) { - pkg := &DubboPackage{ - Body: make([]interface{}, 7), - } - - buf := bytes.NewBuffer(data) - err := pkg.Unmarshal(buf) - if err != nil { - originErr := perrors.Cause(err) - if originErr == hessian2.ErrHeaderNotEnough || originErr == hessian2.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) - } - - if pkg.Header.Type&hessian2.PackageHeartbeat == 0x00 { - // convert params of request - req := pkg.Body.([]interface{}) // length of body should be 7 - if len(req) > 0 { - var dubboVersion, argsTypes string - var args []interface{} - var attachments map[string]interface{} - if req[0] != nil { - dubboVersion = req[0].(string) - } - if req[1] != nil { - pkg.Service.Path = req[1].(string) - } - if req[2] != nil { - pkg.Service.Version = req[2].(string) - } - if req[3] != nil { - pkg.Service.Method = req[3].(string) - } - if req[4] != nil { - argsTypes = req[4].(string) - } - if req[5] != nil { - args = req[5].([]interface{}) - } - if req[6] != nil { - attachments = req[6].(map[string]interface{}) - } - if pkg.Service.Path == "" && attachments[constant.PATH_KEY] != nil && len(attachments[constant.PATH_KEY].(string)) > 0 { - pkg.Service.Path = attachments[constant.PATH_KEY].(string) - } - if inter, ok := attachments[constant.INTERFACE_KEY]; ok && inter != nil { - pkg.Service.Interface = inter.(string) - } else { - pkg.Service.Interface = pkg.Service.Path - } - if attachments[constant.GROUP_KEY] != nil && len(attachments[constant.GROUP_KEY].(string)) > 0 { - pkg.Service.Group = attachments[constant.GROUP_KEY].(string) - } - 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 pkg, hessian2.HEADER_LENGTH + pkg.Header.BodyLen, nil -} - -// Write encode @pkg. -func (p *RpcServerPackageHandler) Write(ss getty.Session, pkg interface{}) ([]byte, error) { - res, ok := pkg.(*DubboPackage) - 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() - if err != nil { - logger.Warnf("binary.Write(res{%#v}) = err{%#v}", res, perrors.WithStack(err)) - return nil, perrors.WithStack(err) - } - - return buf.Bytes(), nil -} diff --git a/protocol/invocation/rpcinvocation.go b/protocol/invocation/rpcinvocation.go index 35d12965e8..4e806324bf 100644 --- a/protocol/invocation/rpcinvocation.go +++ b/protocol/invocation/rpcinvocation.go @@ -23,6 +23,8 @@ import ( ) import ( + "github.com/apache/dubbo-go/common" + "github.com/apache/dubbo-go/common/constant" "github.com/apache/dubbo-go/protocol" ) @@ -186,6 +188,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 576591940d..4a9645e828 100644 --- a/protocol/jsonrpc/http_test.go +++ b/protocol/jsonrpc/http_test.go @@ -75,7 +75,7 @@ func TestHTTPClientCall(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", }) @@ -89,7 +89,7 @@ func TestHTTPClientCall(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", }) @@ -102,7 +102,7 @@ func TestHTTPClientCall(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", }) @@ -114,7 +114,7 @@ func TestHTTPClientCall(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", }) @@ -126,7 +126,7 @@ func TestHTTPClientCall(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", }) @@ -138,7 +138,7 @@ func TestHTTPClientCall(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", }) @@ -149,7 +149,7 @@ func TestHTTPClientCall(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/remoting/codec.go b/remoting/codec.go new file mode 100644 index 0000000000..607d1643cc --- /dev/null +++ b/remoting/codec.go @@ -0,0 +1,45 @@ +/* + * 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 ( + "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 0000000000..848d9cbbcc --- /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 create to a new 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 0000000000..efcfca5586 --- /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 = 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 0000000000..a31e994d74 --- /dev/null +++ b/remoting/exchange_server.go @@ -0,0 +1,55 @@ +/* + * 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 + Url common.URL +} + +// Create ExchangeServer +func NewExchangeServer(url common.URL, server Server) *ExchangeServer { + exchangServer := &ExchangeServer{ + Server: server, + Url: url, + } + 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/config.go b/remoting/getty/config.go similarity index 96% rename from protocol/dubbo/config.go rename to remoting/getty/config.go index b47ec1cc34..dcf59d0821 100644 --- a/protocol/dubbo/config.go +++ b/remoting/getty/config.go @@ -15,7 +15,7 @@ * limitations under the License. */ -package dubbo +package getty import ( "time" @@ -30,7 +30,7 @@ import ( ) type ( - // GettySessionParam is session configuration for getty. + // GettySessionParam is session configuration for getty 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"` @@ -52,6 +52,8 @@ type ( // ServerConfig holds supported types by the multiconfig package ServerConfig struct { + SSLEnabled bool + // session SessionTimeout string `default:"60s" yaml:"session_timeout" json:"session_timeout,omitempty"` sessionTimeout time.Duration @@ -95,7 +97,7 @@ type ( } ) -// GetDefaultClientConfig gets client default configuration. +// GetDefaultClientConfig gets client default configuration func GetDefaultClientConfig() ClientConfig { return ClientConfig{ ReconnectInterval: 0, @@ -123,7 +125,7 @@ func GetDefaultClientConfig() ClientConfig { }} } -// GetDefaultServerConfig gets server default configuration. +// GetDefaultServerConfig gets server default configuration func GetDefaultServerConfig() ServerConfig { return ServerConfig{ SessionTimeout: "180s", @@ -148,7 +150,7 @@ func GetDefaultServerConfig() ServerConfig { } } -// CheckValidity confirm getty sessian params. +// CheckValidity confirm getty sessian params func (c *GettySessionParam) CheckValidity() error { var err error @@ -193,7 +195,7 @@ func (c *ClientConfig) CheckValidity() error { return perrors.WithStack(c.GettySessionParam.CheckValidity()) } -// CheckValidity confirm server params. +// CheckValidity confirm server params func (c *ServerConfig) CheckValidity() error { var err error diff --git a/remoting/getty/dubbo_codec_for_test.go b/remoting/getty/dubbo_codec_for_test.go new file mode 100644 index 0000000000..b91fc9f4cc --- /dev/null +++ b/remoting/getty/dubbo_codec_for_test.go @@ -0,0 +1,276 @@ +/* + * 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 ( + "bytes" + "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/protocol" + "github.com/apache/dubbo-go/protocol/dubbo/impl" + "github.com/apache/dubbo-go/protocol/invocation" + "github.com/apache/dubbo-go/remoting" +) + +func init() { + codec := &DubboTestCodec{} + remoting.RegistryCodec("dubbo", codec) +} + +type DubboTestCodec struct { +} + +// encode request for transport +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 { + 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.PROTOBUF_SERIALIZATION { + header.SerialID = constant.S_Proto + } else { + header.SerialID = constant.S_Hessian2 + } + 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 *DubboTestCodec) 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 *DubboTestCodec) 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 = &impl.ResponsePayload{ + 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 *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, 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 *DubboTestCodec) isRequest(data []byte) bool { + if data[2]&byte(0x80) == 0x00 { + return false + } + return true +} + +// decode request +func (c *DubboTestCodec) 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 + } + 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.(map[string]interface{}) + + //invocation := request.Data.(*invocation.RPCInvocation) + var methodName string + var args []interface{} + attachments := make(map[string]interface{}) + if req[impl.DubboVersionKey] != nil { + //dubbo version + request.Version = req[impl.DubboVersionKey].(string) + } + //path + attachments[constant.PATH_KEY] = pkg.Service.Path + //version + attachments[constant.VERSION_KEY] = pkg.Service.Version + //method + methodName = pkg.Service.Method + args = req[impl.ArgsKey].([]interface{}) + attachments = req[impl.AttachmentsKey].(map[string]interface{}) + 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 *DubboTestCodec) 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 + } + 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 { + if pkg.Err != nil { + error = pkg.Err + } + } else { + response.Status = hessian.Response_OK + //reply(session, p, hessian.PackageHeartbeat) + } + return response, hessian.HEADER_LENGTH + pkg.Header.BodyLen, error + } + 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.(*impl.ResponsePayload).Exception != nil { + rpcResult.Err = pkg.Body.(*impl.ResponsePayload).Exception + response.Error = rpcResult.Err + } + rpcResult.Attrs = pkg.Body.(*impl.ResponsePayload).Attachments + rpcResult.Rest = pkg.Body.(*impl.ResponsePayload).RspObj + } + + 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 0000000000..6af3971f5c --- /dev/null +++ b/remoting/getty/getty_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 getty + +import ( + "math/rand" + "time" +) + +import ( + "github.com/apache/dubbo-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/constant" + "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 + // request timeout + RequestTimeout 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 + fallthrough + case opt.RequestTimeout == 0: + opt.RequestTimeout = 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)) + c.pool.sslEnabled = url.GetParamBool(constant.SSL_ENABLED_KEY, false) + + // 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 0000000000..41ca3108a8 --- /dev/null +++ b/remoting/getty/getty_client_test.go @@ -0,0 +1,492 @@ +/* + * 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) { + 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) { + 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/server.go b/remoting/getty/getty_server.go similarity index 66% rename from protocol/dubbo/server.go rename to remoting/getty/getty_server.go index b693b06f49..7c8fa29a62 100644 --- a/protocol/dubbo/server.go +++ b/remoting/getty/getty_server.go @@ -15,7 +15,7 @@ * limitations under the License. */ -package dubbo +package getty import ( "crypto/tls" @@ -25,7 +25,7 @@ import ( import ( "github.com/apache/dubbo-getty" - "github.com/dubbogo/gost/sync" + gxsync "github.com/dubbogo/gost/sync" perrors "github.com/pkg/errors" "gopkg.in/yaml.v2" ) @@ -35,6 +35,9 @@ import ( "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/invocation" + "github.com/apache/dubbo-go/remoting" ) var ( @@ -42,7 +45,7 @@ var ( srvGrpool *gxsync.TaskPool ) -func init() { +func initServer(protocol string) { // load clientconfig from provider_config // default use dubbo providerConfig := config.GetProviderConfig() @@ -54,7 +57,7 @@ func init() { if protocolConf == nil { logger.Info("protocol_conf default use dubbo config") } else { - dubboConf := protocolConf.(map[interface{}]interface{})[DUBBO] + dubboConf := protocolConf.(map[interface{}]interface{})[protocol] if dubboConf == nil { logger.Warnf("dubboConf is nil") return @@ -73,7 +76,7 @@ func init() { if err := srvConf.CheckValidity(); err != nil { panic(err) } - setServerGrpool() + SetServerGrpool() } // SetServerConfig set dubbo server config. @@ -84,15 +87,16 @@ func SetServerConfig(s ServerConfig) { logger.Warnf("[ServerConfig CheckValidity] error: %v", err) return } - setServerGrpool() + SetServerGrpool() } -// GetServerConfig get dubbo server config. +// GetServerConfig get getty server config. func GetServerConfig() ServerConfig { return *srvConf } -func setServerGrpool() { +// SetServerGrpool set getty server GrPool +func SetServerGrpool() { if srvConf.GrPoolSize > 1 { srvGrpool = gxsync.NewTaskPool( gxsync.WithTaskPoolTaskPoolSize(srvConf.GrPoolSize), @@ -102,19 +106,33 @@ func setServerGrpool() { } } -// Server is dubbo protocol server. +// Server define getty 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 create a new Server. -func NewServer() *Server { - return &Server{ - conf: *srvConf, - rpcHandler: NewRpcServerHandler(srvConf.SessionNumber, srvConf.sessionTimeout), +// NewServer create a new Server +func NewServer(url common.URL, handlers func(*invocation.RPCInvocation) protocol.RPCResult) *Server { + //init + initServer(url.Protocol) + + srvConf.SSLEnabled = url.GetParamBool(constant.SSL_ENABLED_KEY, false) + + s := &Server{ + conf: *srvConf, + addr: url.Location, + codec: remoting.GetCodec(url.Protocol), + requestHandler: handlers, } + + s.rpcHandler = NewRpcServerHandler(s.conf.SessionNumber, s.conf.sessionTimeout, s) + + return s } func (s *Server) newSession(session getty.Session) error { @@ -128,6 +146,23 @@ func (s *Server) newSession(session getty.Session) error { if conf.GettySessionParam.CompressEncoding { session.SetCompressType(getty.CompressZip) } + if _, ok = session.Conn().(*tls.Conn); ok { + session.SetName(conf.GettySessionParam.SessionName) + session.SetMaxMsgLen(conf.GettySessionParam.MaxMsgLen) + session.SetPkgHandler(NewRpcServerPackageHandler(s)) + session.SetEventListener(s.rpcHandler) + session.SetWQLen(conf.GettySessionParam.PkgWQSize) + session.SetReadTimeout(conf.GettySessionParam.tcpReadTimeout) + session.SetWriteTimeout(conf.GettySessionParam.tcpWriteTimeout) + session.SetCronPeriod((int)(conf.sessionTimeout.Nanoseconds() / 1e6)) + session.SetWaitTime(conf.GettySessionParam.waitTimeout) + logger.Debugf("server accepts new session:%s\n", session.Stat()) + session.SetTaskPool(srvGrpool) + return nil + } + if tcpConn, ok = session.Conn().(*net.TCPConn); !ok { + panic(fmt.Sprintf("%s, session.conn{%#v} is not tcp connection\n", session.Stat(), session.Conn())) + } if _, ok = session.Conn().(*tls.Conn); !ok { if tcpConn, ok = session.Conn().(*net.TCPConn); !ok { @@ -155,7 +190,7 @@ func (s *Server) newSession(session getty.Session) error { 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.tcpReadTimeout) @@ -167,18 +202,18 @@ func (s *Server) newSession(session getty.Session) error { return nil } -// Start start dubbo server. -func (s *Server) Start(url common.URL) { +// Start dubbo server. +func (s *Server) Start() { var ( addr string tcpServer getty.Server ) - addr = url.Location - if url.GetParamBool(constant.SSL_ENABLED_KEY, false) { + addr = s.addr + if s.conf.SSLEnabled { tcpServer = getty.NewTCPServer( getty.WithLocalAddress(addr), - getty.WithServerSslEnabled(url.GetParamBool(constant.SSL_ENABLED_KEY, false)), + getty.WithServerSslEnabled(s.conf.SSLEnabled), getty.WithServerTlsConfigBuilder(config.GetServerTlsConfigBuilder()), ) } else { @@ -187,11 +222,11 @@ func (s *Server) Start(url common.URL) { ) } tcpServer.RunEventLoop(s.newSession) - logger.Debugf("s bind addr{%s} ok!", addr) + logger.Debugf("s bind addr{%s} ok!", s.addr) s.tcpServer = tcpServer } -// Stop stop dubbo server. +// Stop dubbo server func (s *Server) Stop() { s.tcpServer.Close() } diff --git a/remoting/getty/listener.go b/remoting/getty/listener.go new file mode 100644 index 0000000000..8d1a63f1c4 --- /dev/null +++ b/remoting/getty/listener.go @@ -0,0 +1,319 @@ +/* + * 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 ( + "fmt" + "sync" + "sync/atomic" + "time" +) + +import ( + "github.com/apache/dubbo-getty" + 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/invocation" + "github.com/apache/dubbo-go/remoting" +) + +// todo: WritePkg_Timeout will entry *.yml +const ( + // WritePkg_Timeout ... + WritePkg_Timeout = 5 * time.Second +) + +var ( + errTooManySessions = perrors.New("too many sessions") +) + +type rpcSession struct { + session getty.Session + reqNum int32 +} + +func (s *rpcSession) AddReqNum(num int32) { + atomic.AddInt32(&s.reqNum, num) +} + +func (s *rpcSession) GetReqNum() int32 { + return atomic.LoadInt32(&s.reqNum) +} + +// ////////////////////////////////////////// +// RpcClientHandler +// ////////////////////////////////////////// + +// RpcClientHandler ... +type RpcClientHandler struct { + conn *gettyRPCClient +} + +// NewRpcClientHandler ... +func NewRpcClientHandler(client *gettyRPCClient) *RpcClientHandler { + return &RpcClientHandler{conn: client} +} + +// OnOpen ... +func (h *RpcClientHandler) OnOpen(session getty.Session) error { + h.conn.addSession(session) + return nil +} + +// OnError ... +func (h *RpcClientHandler) OnError(session getty.Session, err error) { + logger.Infof("session{%s} got error{%v}, will be closed.", session.Stat(), err) + h.conn.removeSession(session) +} + +// OnClose ... +func (h *RpcClientHandler) OnClose(session getty.Session) { + logger.Infof("session{%s} is closing......", session.Stat()) + h.conn.removeSession(session) +} + +// OnMessage ... +func (h *RpcClientHandler) OnMessage(session getty.Session, pkg interface{}) { + result, ok := pkg.(remoting.DecodeResult) + if !ok { + logger.Errorf("illegal package") + return + } + // 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 + } + + 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 result.IsRequest { + logger.Errorf("illegal package for it is response type. {%#v}", pkg) + return + } + + logger.Debugf("get rpc response{%#v}", p) + + h.conn.updateSession(session) + + h.conn.pool.rpcClient.responseHandler.Handler(p) +} + +// OnCron ... +func (h *RpcClientHandler) OnCron(session getty.Session) { + rpcSession, err := h.conn.getClientRpcSession(session) + if err != nil { + logger.Errorf("client.getClientSession(session{%s}) = error{%v}", + session.Stat(), perrors.WithStack(err)) + return + } + 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) + return + } + + h.conn.pool.rpcClient.heartbeat(session) +} + +// ////////////////////////////////////////// +// RpcServerHandler +// ////////////////////////////////////////// + +// RpcServerHandler implement EventListener of getty. +type RpcServerHandler struct { + maxSessionNum int + sessionTimeout time.Duration + sessionMap map[getty.Session]*rpcSession + rwlock sync.RWMutex + server *Server +} + +// NewRpcServerHandler ... +func NewRpcServerHandler(maxSessionNum int, sessionTimeout time.Duration, serverP *Server) *RpcServerHandler { + return &RpcServerHandler{ + maxSessionNum: maxSessionNum, + sessionTimeout: sessionTimeout, + sessionMap: make(map[getty.Session]*rpcSession), + server: serverP, + } +} + +// OnOpen ... +func (h *RpcServerHandler) OnOpen(session getty.Session) error { + var err error + h.rwlock.RLock() + if h.maxSessionNum <= len(h.sessionMap) { + err = errTooManySessions + } + h.rwlock.RUnlock() + if err != nil { + return perrors.WithStack(err) + } + + logger.Infof("got session:%s", session.Stat()) + h.rwlock.Lock() + h.sessionMap[session] = &rpcSession{session: session} + h.rwlock.Unlock() + return nil +} + +// OnError ... +func (h *RpcServerHandler) OnError(session getty.Session, err error) { + logger.Infof("session{%s} got error{%v}, will be closed.", session.Stat(), err) + h.rwlock.Lock() + delete(h.sessionMap, session) + h.rwlock.Unlock() +} + +// OnClose ... +func (h *RpcServerHandler) OnClose(session getty.Session) { + logger.Infof("session{%s} is closing......", session.Stat()) + h.rwlock.Lock() + delete(h.sessionMap, session) + h.rwlock.Unlock() +} + +// OnMessage ... +func (h *RpcServerHandler) OnMessage(session getty.Session, pkg interface{}) { + h.rwlock.Lock() + if _, ok := h.sessionMap[session]; ok { + h.sessionMap[session].reqNum++ + } + h.rwlock.Unlock() + + decodeResult, ok := pkg.(remoting.DecodeResult) + if !ok { + logger.Errorf("illegal package{%#v}", pkg) + return + } + 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" + + // 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 { + resp.Status = hessian.Response_SERVER_ERROR + if err, ok := e.(error); ok { + logger.Errorf("OnMessage panic: %+v", perrors.WithStack(err)) + resp.Error = perrors.WithStack(err) + } else if err, ok := e.(string); ok { + logger.Errorf("OnMessage panic: %+v", perrors.New(err)) + resp.Error = perrors.New(err) + } else { + logger.Errorf("OnMessage panic: %+v, this is impossible.", e) + resp.Error = fmt.Errorf("OnMessage panic unknow exception. %+v", e) + } + + if !req.TwoWay { + return + } + reply(session, resp, hessian.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 ... +func (h *RpcServerHandler) OnCron(session getty.Session) { + var ( + flag bool + active time.Time + ) + + h.rwlock.RLock() + if _, ok := h.sessionMap[session]; ok { + active = session.GetActive() + if h.sessionTimeout.Nanoseconds() < time.Since(active).Nanoseconds() { + flag = true + logger.Warnf("session{%s} timeout{%s}, reqNum{%d}", + session.Stat(), time.Since(active).String(), h.sessionMap[session].reqNum) + } + } + h.rwlock.RUnlock() + + if flag { + h.rwlock.Lock() + delete(h.sessionMap, session) + h.rwlock.Unlock() + session.Close() + } +} + +func reply(session getty.Session, resp *remoting.Response, tp hessian.PackageType) { + + if err := session.WritePkg(resp, WritePkg_Timeout); err != nil { + logger.Errorf("WritePkg error: %#v, %#v", perrors.WithStack(err), resp) + } +} diff --git a/protocol/dubbo/listener_test.go b/remoting/getty/listener_test.go similarity index 71% rename from protocol/dubbo/listener_test.go rename to remoting/getty/listener_test.go index 5ab73fd465..7e7ac5fed4 100644 --- a/protocol/dubbo/listener_test.go +++ b/remoting/getty/listener_test.go @@ -15,21 +15,22 @@ * limitations under the License. */ -package dubbo +package getty import ( + "context" "testing" ) import ( - "github.com/opentracing/opentracing-go" - "github.com/opentracing/opentracing-go/mocktracer" "github.com/stretchr/testify/assert" ) import ( "github.com/apache/dubbo-go/common/constant" "github.com/apache/dubbo-go/protocol/invocation" + "github.com/opentracing/opentracing-go" + "github.com/opentracing/opentracing-go/mocktracer" ) // test rebuild the ctx @@ -47,7 +48,9 @@ func TestRebuildCtx(t *testing.T) { span, ctx := opentracing.StartSpanFromContext(ctx, "Test-Client") - injectTraceCtx(span, inv) + err := injectTraceCtx(span, inv) + assert.NoError(t, err) + // rebuild the context success inv = invocation.NewRPCInvocation("MethodName", []interface{}{"OK", "Hello"}, attach) ctx = rebuildCtx(inv) @@ -55,3 +58,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(filterContext(inv.Attachments()))) + if err == nil { + ctx = context.WithValue(ctx, constant.TRACING_REMOTE_SPAN_CTX, spanCtx) + } + return ctx +} diff --git a/remoting/getty/opentracing.go b/remoting/getty/opentracing.go new file mode 100644 index 0000000000..7db733cbe9 --- /dev/null +++ b/remoting/getty/opentracing.go @@ -0,0 +1,60 @@ +/* + * 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 ( + "github.com/opentracing/opentracing-go" +) +import ( + invocation_impl "github.com/apache/dubbo-go/protocol/invocation" +) + +func injectTraceCtx(currentSpan opentracing.Span, inv *invocation_impl.RPCInvocation) error { + // inject opentracing ctx + traceAttachments := filterContext(inv.Attachments()) + carrier := opentracing.TextMapCarrier(traceAttachments) + err := opentracing.GlobalTracer().Inject(currentSpan.Context(), opentracing.TextMap, carrier) + if err == nil { + fillTraceAttachments(inv.Attachments(), traceAttachments) + } + return err +} + +func extractTraceCtx(inv *invocation_impl.RPCInvocation) (opentracing.SpanContext, error) { + traceAttachments := filterContext(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(traceAttachments)) + return spanCtx, err +} + +func filterContext(attachments map[string]interface{}) map[string]string { + var traceAttchment = make(map[string]string) + for k, v := range attachments { + if r, ok := v.(string); ok { + traceAttchment[k] = r + } + } + return traceAttchment +} + +func fillTraceAttachments(attachments map[string]interface{}, traceAttachment map[string]string) { + for k, v := range traceAttachment { + attachments[k] = v + } +} diff --git a/protocol/dubbo/pool.go b/remoting/getty/pool.go similarity index 91% rename from protocol/dubbo/pool.go rename to remoting/getty/pool.go index 6a7d211b49..464cff956e 100644 --- a/protocol/dubbo/pool.go +++ b/remoting/getty/pool.go @@ -15,7 +15,7 @@ * limitations under the License. */ -package dubbo +package getty import ( "crypto/tls" @@ -38,10 +38,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 @@ -54,7 +54,7 @@ var ( errClientPoolClosed = perrors.New("client pool closed") ) -func newGettyRPCClientConn(pool *gettyRPCClientPool, protocol, addr string) (*gettyRPCClient, error) { +func newGettyRPCClientConn(pool *gettyRPCClientPool, addr string) (*gettyRPCClient, error) { var ( gettyClient getty.Client sslEnabled bool @@ -76,7 +76,6 @@ func newGettyRPCClientConn(pool *gettyRPCClientPool, protocol, addr string) (*ge ) } c := &gettyRPCClient{ - protocol: protocol, addr: addr, pool: pool, gettyClient: gettyClient, @@ -117,7 +116,6 @@ func (c *gettyRPCClient) newSession(session getty.Session) error { conf ClientConfig sslEnabled bool ) - conf = c.pool.rpcClient.conf sslEnabled = c.pool.sslEnabled if conf.GettySessionParam.CompressEncoding { @@ -255,25 +253,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 { @@ -338,7 +336,8 @@ func newGettyRPCClientConnPool(rpcClient *Client, size int, ttl time.Duration) * rpcClient: rpcClient, size: size, ttl: int64(ttl.Seconds()), - conns: make([]*gettyRPCClient, 0, 16), + // init capacity : 2 + conns: make([]*gettyRPCClient, 0, 2), } } @@ -352,11 +351,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) } @@ -369,10 +372,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) @@ -389,21 +397,17 @@ 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 // p.remove(conn) diff --git a/remoting/getty/readwriter.go b/remoting/getty/readwriter.go new file mode 100644 index 0000000000..c6585c2dc6 --- /dev/null +++ b/remoting/getty/readwriter.go @@ -0,0 +1,136 @@ +/* + * 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 ( + "reflect" +) + +import ( + "github.com/apache/dubbo-getty" + hessian "github.com/apache/dubbo-go-hessian2" + perrors "github.com/pkg/errors" +) + +import ( + "github.com/apache/dubbo-go/common/logger" + "github.com/apache/dubbo-go/remoting" +) + +//////////////////////////////////////////// +// RpcClientPackageHandler +//////////////////////////////////////////// + +// RpcClientPackageHandler Read data from server and Write data to server +type RpcClientPackageHandler struct { + client *Client +} + +// NewRpcClientPackageHandler create a RpcClientPackageHandler +func NewRpcClientPackageHandler(client *Client) *RpcClientPackageHandler { + return &RpcClientPackageHandler{client: client} +} + +// Read data from server. if the package size from server is larger than 4096 byte, server will read 4096 byte +// and send to client each time. the Read can assemble it. +func (p *RpcClientPackageHandler) Read(ss getty.Session, data []byte) (interface{}, int, error) { + resp, length, err := (p.client.codec).Decode(data) + //err := pkg.Unmarshal(buf, p.client) + if err != nil { + err = perrors.Cause(err) + 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, length, err + } + + return resp, length, nil +} + +// Write send the data to server +func (p *RpcClientPackageHandler) Write(ss getty.Session, pkg interface{}) ([]byte, error) { + req, ok := pkg.(*remoting.Request) + if !ok { + logger.Errorf("illegal pkg:%+v\n", pkg) + return nil, perrors.New("invalid rpc request") + } + + 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 +} + +//////////////////////////////////////////// +// RpcServerPackageHandler +//////////////////////////////////////////// + +//var ( +// rpcServerPkgHandler = &RpcServerPackageHandler{} +//) + +// RpcServerPackageHandler Read data from client and Write data to client +type RpcServerPackageHandler struct { + server *Server +} + +func NewRpcServerPackageHandler(server *Server) *RpcServerPackageHandler { + return &RpcServerPackageHandler{server: server} +} + +// Read data from client. if the package size from client is larger than 4096 byte, client will read 4096 byte +// and send to client each time. the Read can assemble it. +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) + + if err != nil { + 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, err + } + + return req, length, err +} + +// Write send the data to client +func (p *RpcServerPackageHandler) Write(ss getty.Session, pkg interface{}) ([]byte, error) { + 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 := (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 +} diff --git a/test/integrate/dubbo/go-client/go.mod b/test/integrate/dubbo/go-client/go.mod index b0be45ae9c..8428a513ad 100644 --- a/test/integrate/dubbo/go-client/go.mod +++ b/test/integrate/dubbo/go-client/go.mod @@ -1,7 +1,5 @@ module github.com/apache/dubbo-go/test/integrate/dubbo/go-client -require ( - github.com/apache/dubbo-go-hessian2 v1.6.0-rc1.0.20200906044240-6c1fb5c3bd44 -) +require github.com/apache/dubbo-go-hessian2 v1.6.0-rc1.0.20200906044240-6c1fb5c3bd44 go 1.13 diff --git a/test/integrate/dubbo/go-client/go.sum b/test/integrate/dubbo/go-client/go.sum new file mode 100644 index 0000000000..7bb51161b1 --- /dev/null +++ b/test/integrate/dubbo/go-client/go.sum @@ -0,0 +1,10 @@ +github.com/apache/dubbo-go-hessian2 v1.6.0-rc1.0.20200906044240-6c1fb5c3bd44/go.mod h1:7rEw9guWABQa6Aqb8HeZcsYPHsOS7XT1qtJvkmI6c5w= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/dubbogo/gost v1.9.0/go.mod h1:pPTjVyoJan3aPxBPNUX0ADkXjPibLo+/Ib0/fADXSG8= +github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= diff --git a/test/integrate/dubbo/go-server/go.mod b/test/integrate/dubbo/go-server/go.mod index 6c530f6a59..6a56b189e1 100644 --- a/test/integrate/dubbo/go-server/go.mod +++ b/test/integrate/dubbo/go-server/go.mod @@ -1,7 +1,5 @@ module github.com/apache/dubbo-go/test/integrate/dubbo/go-server -require ( - github.com/apache/dubbo-go-hessian2 v1.6.0-rc1.0.20200906044240-6c1fb5c3bd44 -) +require github.com/apache/dubbo-go-hessian2 v1.6.0-rc1.0.20200906044240-6c1fb5c3bd44 go 1.13 diff --git a/test/integrate/dubbo/go-server/go.sum b/test/integrate/dubbo/go-server/go.sum new file mode 100644 index 0000000000..7bb51161b1 --- /dev/null +++ b/test/integrate/dubbo/go-server/go.sum @@ -0,0 +1,10 @@ +github.com/apache/dubbo-go-hessian2 v1.6.0-rc1.0.20200906044240-6c1fb5c3bd44/go.mod h1:7rEw9guWABQa6Aqb8HeZcsYPHsOS7XT1qtJvkmI6c5w= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/dubbogo/gost v1.9.0/go.mod h1:pPTjVyoJan3aPxBPNUX0ADkXjPibLo+/Ib0/fADXSG8= +github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=