github.com/annwntech/go-micro/v2@v2.9.5/client/rpc_client.go (about)

     1  package client
     2  
     3  import (
     4  	"context"
     5  	"fmt"
     6  	"sync/atomic"
     7  	"time"
     8  
     9  	"github.com/google/uuid"
    10  	"github.com/annwntech/go-micro/v2/broker"
    11  	"github.com/annwntech/go-micro/v2/client/selector"
    12  	"github.com/annwntech/go-micro/v2/codec"
    13  	raw "github.com/annwntech/go-micro/v2/codec/bytes"
    14  	"github.com/annwntech/go-micro/v2/errors"
    15  	"github.com/annwntech/go-micro/v2/metadata"
    16  	"github.com/annwntech/go-micro/v2/registry"
    17  	"github.com/annwntech/go-micro/v2/transport"
    18  	"github.com/annwntech/go-micro/v2/util/buf"
    19  	"github.com/annwntech/go-micro/v2/util/net"
    20  	"github.com/annwntech/go-micro/v2/util/pool"
    21  )
    22  
    23  type rpcClient struct {
    24  	once atomic.Value
    25  	opts Options
    26  	pool pool.Pool
    27  	seq  uint64
    28  }
    29  
    30  func newRpcClient(opt ...Option) Client {
    31  	opts := NewOptions(opt...)
    32  
    33  	p := pool.NewPool(
    34  		pool.Size(opts.PoolSize),
    35  		pool.TTL(opts.PoolTTL),
    36  		pool.Transport(opts.Transport),
    37  	)
    38  
    39  	rc := &rpcClient{
    40  		opts: opts,
    41  		pool: p,
    42  		seq:  0,
    43  	}
    44  	rc.once.Store(false)
    45  
    46  	c := Client(rc)
    47  
    48  	// wrap in reverse
    49  	for i := len(opts.Wrappers); i > 0; i-- {
    50  		c = opts.Wrappers[i-1](c)
    51  	}
    52  
    53  	return c
    54  }
    55  
    56  func (r *rpcClient) newCodec(contentType string) (codec.NewCodec, error) {
    57  	if c, ok := r.opts.Codecs[contentType]; ok {
    58  		return c, nil
    59  	}
    60  	if cf, ok := DefaultCodecs[contentType]; ok {
    61  		return cf, nil
    62  	}
    63  	return nil, fmt.Errorf("Unsupported Content-Type: %s", contentType)
    64  }
    65  
    66  func (r *rpcClient) call(ctx context.Context, node *registry.Node, req Request, resp interface{}, opts CallOptions) error {
    67  	address := node.Address
    68  
    69  	msg := &transport.Message{
    70  		Header: make(map[string]string),
    71  	}
    72  
    73  	md, ok := metadata.FromContext(ctx)
    74  	if ok {
    75  		for k, v := range md {
    76  			// don't copy Micro-Topic header, that used for pub/sub
    77  			// this fix case then client uses the same context that received in subscriber
    78  			if k == "Micro-Topic" {
    79  				continue
    80  			}
    81  			msg.Header[k] = v
    82  		}
    83  	}
    84  
    85  	// set timeout in nanoseconds
    86  	msg.Header["Timeout"] = fmt.Sprintf("%d", opts.RequestTimeout)
    87  	// set the content type for the request
    88  	msg.Header["Content-Type"] = req.ContentType()
    89  	// set the accept header
    90  	msg.Header["Accept"] = req.ContentType()
    91  
    92  	// setup old protocol
    93  	cf := setupProtocol(msg, node)
    94  
    95  	// no codec specified
    96  	if cf == nil {
    97  		var err error
    98  		cf, err = r.newCodec(req.ContentType())
    99  		if err != nil {
   100  			return errors.InternalServerError("go.micro.client", err.Error())
   101  		}
   102  	}
   103  
   104  	dOpts := []transport.DialOption{
   105  		transport.WithStream(),
   106  	}
   107  
   108  	if opts.DialTimeout >= 0 {
   109  		dOpts = append(dOpts, transport.WithTimeout(opts.DialTimeout))
   110  	}
   111  
   112  	c, err := r.pool.Get(address, dOpts...)
   113  	if err != nil {
   114  		return errors.InternalServerError("go.micro.client", "connection error: %v", err)
   115  	}
   116  
   117  	seq := atomic.AddUint64(&r.seq, 1) - 1
   118  	codec := newRpcCodec(msg, c, cf, "")
   119  
   120  	rsp := &rpcResponse{
   121  		socket: c,
   122  		codec:  codec,
   123  	}
   124  
   125  	stream := &rpcStream{
   126  		id:       fmt.Sprintf("%v", seq),
   127  		context:  ctx,
   128  		request:  req,
   129  		response: rsp,
   130  		codec:    codec,
   131  		closed:   make(chan bool),
   132  		release:  func(err error) { r.pool.Release(c, err) },
   133  		sendEOS:  false,
   134  	}
   135  	// close the stream on exiting this function
   136  	defer stream.Close()
   137  
   138  	// wait for error response
   139  	ch := make(chan error, 1)
   140  
   141  	go func() {
   142  		defer func() {
   143  			if r := recover(); r != nil {
   144  				ch <- errors.InternalServerError("go.micro.client", "panic recovered: %v", r)
   145  			}
   146  		}()
   147  
   148  		// send request
   149  		if err := stream.Send(req.Body()); err != nil {
   150  			ch <- err
   151  			return
   152  		}
   153  
   154  		// recv request
   155  		if err := stream.Recv(resp); err != nil {
   156  			ch <- err
   157  			return
   158  		}
   159  
   160  		// success
   161  		ch <- nil
   162  	}()
   163  
   164  	var grr error
   165  
   166  	select {
   167  	case err := <-ch:
   168  		return err
   169  	case <-ctx.Done():
   170  		grr = errors.Timeout("go.micro.client", fmt.Sprintf("%v", ctx.Err()))
   171  	}
   172  
   173  	// set the stream error
   174  	if grr != nil {
   175  		stream.Lock()
   176  		stream.err = grr
   177  		stream.Unlock()
   178  
   179  		return grr
   180  	}
   181  
   182  	return nil
   183  }
   184  
   185  func (r *rpcClient) stream(ctx context.Context, node *registry.Node, req Request, opts CallOptions) (Stream, error) {
   186  	address := node.Address
   187  
   188  	msg := &transport.Message{
   189  		Header: make(map[string]string),
   190  	}
   191  
   192  	md, ok := metadata.FromContext(ctx)
   193  	if ok {
   194  		for k, v := range md {
   195  			msg.Header[k] = v
   196  		}
   197  	}
   198  
   199  	// set timeout in nanoseconds
   200  	if opts.StreamTimeout > time.Duration(0) {
   201  		msg.Header["Timeout"] = fmt.Sprintf("%d", opts.StreamTimeout)
   202  	}
   203  	// set the content type for the request
   204  	msg.Header["Content-Type"] = req.ContentType()
   205  	// set the accept header
   206  	msg.Header["Accept"] = req.ContentType()
   207  
   208  	// set old codecs
   209  	cf := setupProtocol(msg, node)
   210  
   211  	// no codec specified
   212  	if cf == nil {
   213  		var err error
   214  		cf, err = r.newCodec(req.ContentType())
   215  		if err != nil {
   216  			return nil, errors.InternalServerError("go.micro.client", err.Error())
   217  		}
   218  	}
   219  
   220  	dOpts := []transport.DialOption{
   221  		transport.WithStream(),
   222  	}
   223  
   224  	if opts.DialTimeout >= 0 {
   225  		dOpts = append(dOpts, transport.WithTimeout(opts.DialTimeout))
   226  	}
   227  
   228  	c, err := r.opts.Transport.Dial(address, dOpts...)
   229  	if err != nil {
   230  		return nil, errors.InternalServerError("go.micro.client", "connection error: %v", err)
   231  	}
   232  
   233  	// increment the sequence number
   234  	seq := atomic.AddUint64(&r.seq, 1) - 1
   235  	id := fmt.Sprintf("%v", seq)
   236  
   237  	// create codec with stream id
   238  	codec := newRpcCodec(msg, c, cf, id)
   239  
   240  	rsp := &rpcResponse{
   241  		socket: c,
   242  		codec:  codec,
   243  	}
   244  
   245  	// set request codec
   246  	if r, ok := req.(*rpcRequest); ok {
   247  		r.codec = codec
   248  	}
   249  
   250  	stream := &rpcStream{
   251  		id:       id,
   252  		context:  ctx,
   253  		request:  req,
   254  		response: rsp,
   255  		codec:    codec,
   256  		// used to close the stream
   257  		closed: make(chan bool),
   258  		// signal the end of stream,
   259  		sendEOS: true,
   260  		// release func
   261  		release: func(err error) { c.Close() },
   262  	}
   263  
   264  	// wait for error response
   265  	ch := make(chan error, 1)
   266  
   267  	go func() {
   268  		// send the first message
   269  		ch <- stream.Send(req.Body())
   270  	}()
   271  
   272  	var grr error
   273  
   274  	select {
   275  	case err := <-ch:
   276  		grr = err
   277  	case <-ctx.Done():
   278  		grr = errors.Timeout("go.micro.client", fmt.Sprintf("%v", ctx.Err()))
   279  	}
   280  
   281  	if grr != nil {
   282  		// set the error
   283  		stream.Lock()
   284  		stream.err = grr
   285  		stream.Unlock()
   286  
   287  		// close the stream
   288  		stream.Close()
   289  		return nil, grr
   290  	}
   291  
   292  	return stream, nil
   293  }
   294  
   295  func (r *rpcClient) Init(opts ...Option) error {
   296  	size := r.opts.PoolSize
   297  	ttl := r.opts.PoolTTL
   298  	tr := r.opts.Transport
   299  
   300  	for _, o := range opts {
   301  		o(&r.opts)
   302  	}
   303  
   304  	// update pool configuration if the options changed
   305  	if size != r.opts.PoolSize || ttl != r.opts.PoolTTL || tr != r.opts.Transport {
   306  		// close existing pool
   307  		r.pool.Close()
   308  		// create new pool
   309  		r.pool = pool.NewPool(
   310  			pool.Size(r.opts.PoolSize),
   311  			pool.TTL(r.opts.PoolTTL),
   312  			pool.Transport(r.opts.Transport),
   313  		)
   314  	}
   315  
   316  	return nil
   317  }
   318  
   319  func (r *rpcClient) Options() Options {
   320  	return r.opts
   321  }
   322  
   323  // next returns an iterator for the next nodes to call
   324  func (r *rpcClient) next(request Request, opts CallOptions) (selector.Next, error) {
   325  	// try get the proxy
   326  	service, address, _ := net.Proxy(request.Service(), opts.Address)
   327  
   328  	// return remote address
   329  	if len(address) > 0 {
   330  		nodes := make([]*registry.Node, len(address))
   331  
   332  		for i, addr := range address {
   333  			nodes[i] = &registry.Node{
   334  				Address: addr,
   335  				// Set the protocol
   336  				Metadata: map[string]string{
   337  					"protocol": "mucp",
   338  				},
   339  			}
   340  		}
   341  
   342  		// crude return method
   343  		return func() (*registry.Node, error) {
   344  			return nodes[time.Now().Unix()%int64(len(nodes))], nil
   345  		}, nil
   346  	}
   347  
   348  	// get next nodes from the selector
   349  	next, err := r.opts.Selector.Select(service, opts.SelectOptions...)
   350  	if err != nil {
   351  		if err == selector.ErrNotFound {
   352  			return nil, errors.InternalServerError("go.micro.client", "service %s: %s", service, err.Error())
   353  		}
   354  		return nil, errors.InternalServerError("go.micro.client", "error selecting %s node: %s", service, err.Error())
   355  	}
   356  
   357  	return next, nil
   358  }
   359  
   360  func (r *rpcClient) Call(ctx context.Context, request Request, response interface{}, opts ...CallOption) error {
   361  	// make a copy of call opts
   362  	callOpts := r.opts.CallOptions
   363  	for _, opt := range opts {
   364  		opt(&callOpts)
   365  	}
   366  
   367  	next, err := r.next(request, callOpts)
   368  	if err != nil {
   369  		return err
   370  	}
   371  
   372  	// check if we already have a deadline
   373  	d, ok := ctx.Deadline()
   374  	if !ok {
   375  		// no deadline so we create a new one
   376  		var cancel context.CancelFunc
   377  		ctx, cancel = context.WithTimeout(ctx, callOpts.RequestTimeout)
   378  		defer cancel()
   379  	} else {
   380  		// got a deadline so no need to setup context
   381  		// but we need to set the timeout we pass along
   382  		opt := WithRequestTimeout(d.Sub(time.Now()))
   383  		opt(&callOpts)
   384  	}
   385  
   386  	// should we noop right here?
   387  	select {
   388  	case <-ctx.Done():
   389  		return errors.Timeout("go.micro.client", fmt.Sprintf("%v", ctx.Err()))
   390  	default:
   391  	}
   392  
   393  	// make copy of call method
   394  	rcall := r.call
   395  
   396  	// wrap the call in reverse
   397  	for i := len(callOpts.CallWrappers); i > 0; i-- {
   398  		rcall = callOpts.CallWrappers[i-1](rcall)
   399  	}
   400  
   401  	// return errors.New("go.micro.client", "request timeout", 408)
   402  	call := func(i int) error {
   403  		// call backoff first. Someone may want an initial start delay
   404  		t, err := callOpts.Backoff(ctx, request, i)
   405  		if err != nil {
   406  			return errors.InternalServerError("go.micro.client", "backoff error: %v", err.Error())
   407  		}
   408  
   409  		// only sleep if greater than 0
   410  		if t.Seconds() > 0 {
   411  			time.Sleep(t)
   412  		}
   413  
   414  		// select next node
   415  		node, err := next()
   416  		service := request.Service()
   417  		if err != nil {
   418  			if err == selector.ErrNotFound {
   419  				return errors.InternalServerError("go.micro.client", "service %s: %s", service, err.Error())
   420  			}
   421  			return errors.InternalServerError("go.micro.client", "error getting next %s node: %s", service, err.Error())
   422  		}
   423  
   424  		// make the call
   425  		err = rcall(ctx, node, request, response, callOpts)
   426  		r.opts.Selector.Mark(service, node, err)
   427  		return err
   428  	}
   429  
   430  	// get the retries
   431  	retries := callOpts.Retries
   432  
   433  	// disable retries when using a proxy
   434  	if _, _, ok := net.Proxy(request.Service(), callOpts.Address); ok {
   435  		retries = 0
   436  	}
   437  
   438  	ch := make(chan error, retries+1)
   439  	var gerr error
   440  
   441  	for i := 0; i <= retries; i++ {
   442  		go func(i int) {
   443  			ch <- call(i)
   444  		}(i)
   445  
   446  		select {
   447  		case <-ctx.Done():
   448  			return errors.Timeout("go.micro.client", fmt.Sprintf("call timeout: %v", ctx.Err()))
   449  		case err := <-ch:
   450  			// if the call succeeded lets bail early
   451  			if err == nil {
   452  				return nil
   453  			}
   454  
   455  			retry, rerr := callOpts.Retry(ctx, request, i, err)
   456  			if rerr != nil {
   457  				return rerr
   458  			}
   459  
   460  			if !retry {
   461  				return err
   462  			}
   463  
   464  			gerr = err
   465  		}
   466  	}
   467  
   468  	return gerr
   469  }
   470  
   471  func (r *rpcClient) Stream(ctx context.Context, request Request, opts ...CallOption) (Stream, error) {
   472  	// make a copy of call opts
   473  	callOpts := r.opts.CallOptions
   474  	for _, opt := range opts {
   475  		opt(&callOpts)
   476  	}
   477  
   478  	next, err := r.next(request, callOpts)
   479  	if err != nil {
   480  		return nil, err
   481  	}
   482  
   483  	// should we noop right here?
   484  	select {
   485  	case <-ctx.Done():
   486  		return nil, errors.Timeout("go.micro.client", fmt.Sprintf("%v", ctx.Err()))
   487  	default:
   488  	}
   489  
   490  	call := func(i int) (Stream, error) {
   491  		// call backoff first. Someone may want an initial start delay
   492  		t, err := callOpts.Backoff(ctx, request, i)
   493  		if err != nil {
   494  			return nil, errors.InternalServerError("go.micro.client", "backoff error: %v", err.Error())
   495  		}
   496  
   497  		// only sleep if greater than 0
   498  		if t.Seconds() > 0 {
   499  			time.Sleep(t)
   500  		}
   501  
   502  		node, err := next()
   503  		service := request.Service()
   504  		if err != nil {
   505  			if err == selector.ErrNotFound {
   506  				return nil, errors.InternalServerError("go.micro.client", "service %s: %s", service, err.Error())
   507  			}
   508  			return nil, errors.InternalServerError("go.micro.client", "error getting next %s node: %s", service, err.Error())
   509  		}
   510  
   511  		stream, err := r.stream(ctx, node, request, callOpts)
   512  		r.opts.Selector.Mark(service, node, err)
   513  		return stream, err
   514  	}
   515  
   516  	type response struct {
   517  		stream Stream
   518  		err    error
   519  	}
   520  
   521  	// get the retries
   522  	retries := callOpts.Retries
   523  
   524  	// disable retries when using a proxy
   525  	if _, _, ok := net.Proxy(request.Service(), callOpts.Address); ok {
   526  		retries = 0
   527  	}
   528  
   529  	ch := make(chan response, retries+1)
   530  	var grr error
   531  
   532  	for i := 0; i <= retries; i++ {
   533  		go func(i int) {
   534  			s, err := call(i)
   535  			ch <- response{s, err}
   536  		}(i)
   537  
   538  		select {
   539  		case <-ctx.Done():
   540  			return nil, errors.Timeout("go.micro.client", fmt.Sprintf("call timeout: %v", ctx.Err()))
   541  		case rsp := <-ch:
   542  			// if the call succeeded lets bail early
   543  			if rsp.err == nil {
   544  				return rsp.stream, nil
   545  			}
   546  
   547  			retry, rerr := callOpts.Retry(ctx, request, i, rsp.err)
   548  			if rerr != nil {
   549  				return nil, rerr
   550  			}
   551  
   552  			if !retry {
   553  				return nil, rsp.err
   554  			}
   555  
   556  			grr = rsp.err
   557  		}
   558  	}
   559  
   560  	return nil, grr
   561  }
   562  
   563  func (r *rpcClient) Publish(ctx context.Context, msg Message, opts ...PublishOption) error {
   564  	options := PublishOptions{
   565  		Context: context.Background(),
   566  	}
   567  	for _, o := range opts {
   568  		o(&options)
   569  	}
   570  
   571  	md, ok := metadata.FromContext(ctx)
   572  	if !ok {
   573  		md = make(map[string]string)
   574  	}
   575  
   576  	id := uuid.New().String()
   577  	md["Content-Type"] = msg.ContentType()
   578  	md["Micro-Topic"] = msg.Topic()
   579  	md["Micro-Id"] = id
   580  
   581  	// set the topic
   582  	topic := msg.Topic()
   583  
   584  	// get the exchange
   585  	if len(options.Exchange) > 0 {
   586  		topic = options.Exchange
   587  	}
   588  
   589  	// encode message body
   590  	cf, err := r.newCodec(msg.ContentType())
   591  	if err != nil {
   592  		return errors.InternalServerError("go.micro.client", err.Error())
   593  	}
   594  
   595  	var body []byte
   596  
   597  	// passed in raw data
   598  	if d, ok := msg.Payload().(*raw.Frame); ok {
   599  		body = d.Data
   600  	} else {
   601  		// new buffer
   602  		b := buf.New(nil)
   603  
   604  		if err := cf(b).Write(&codec.Message{
   605  			Target: topic,
   606  			Type:   codec.Event,
   607  			Header: map[string]string{
   608  				"Micro-Id":    id,
   609  				"Micro-Topic": msg.Topic(),
   610  			},
   611  		}, msg.Payload()); err != nil {
   612  			return errors.InternalServerError("go.micro.client", err.Error())
   613  		}
   614  
   615  		// set the body
   616  		body = b.Bytes()
   617  	}
   618  
   619  	if !r.once.Load().(bool) {
   620  		if err = r.opts.Broker.Connect(); err != nil {
   621  			return errors.InternalServerError("go.micro.client", err.Error())
   622  		}
   623  		r.once.Store(true)
   624  	}
   625  
   626  	return r.opts.Broker.Publish(topic, &broker.Message{
   627  		Header: md,
   628  		Body:   body,
   629  	}, broker.PublishContext(options.Context))
   630  }
   631  
   632  func (r *rpcClient) NewMessage(topic string, message interface{}, opts ...MessageOption) Message {
   633  	return newMessage(topic, message, r.opts.ContentType, opts...)
   634  }
   635  
   636  func (r *rpcClient) NewRequest(service, method string, request interface{}, reqOpts ...RequestOption) Request {
   637  	return newRequest(service, method, request, r.opts.ContentType, reqOpts...)
   638  }
   639  
   640  func (r *rpcClient) String() string {
   641  	return "mucp"
   642  }