github.com/tickoalcantara12/micro/v3@v3.0.0-20221007104245-9d75b9bcbab9/service/client/mucp/mucp.go (about)

     1  // Copyright 2020 Asim Aslam
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //     https://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  //
    15  // Original source: github.com/micro/go-micro/v3/client/mucp/mucp.go
    16  
    17  // Package mucp provides a transport agnostic RPC client
    18  package mucp
    19  
    20  import (
    21  	"context"
    22  	"fmt"
    23  	"sync/atomic"
    24  	"time"
    25  
    26  	"github.com/google/uuid"
    27  	"github.com/tickoalcantara12/micro/v3/service/broker"
    28  	"github.com/tickoalcantara12/micro/v3/service/client"
    29  	"github.com/tickoalcantara12/micro/v3/service/context/metadata"
    30  	"github.com/tickoalcantara12/micro/v3/service/errors"
    31  	"github.com/tickoalcantara12/micro/v3/service/network/transport"
    32  	"github.com/tickoalcantara12/micro/v3/util/buf"
    33  	"github.com/tickoalcantara12/micro/v3/util/codec"
    34  	raw "github.com/tickoalcantara12/micro/v3/util/codec/bytes"
    35  	"github.com/tickoalcantara12/micro/v3/util/pool"
    36  )
    37  
    38  type rpcClient struct {
    39  	once atomic.Value
    40  	opts client.Options
    41  	pool pool.Pool
    42  	seq  uint64
    43  }
    44  
    45  // NewClient returns a new micro client interface
    46  func NewClient(opt ...client.Option) client.Client {
    47  	opts := client.NewOptions(opt...)
    48  
    49  	p := pool.NewPool(
    50  		pool.Size(opts.PoolSize),
    51  		pool.TTL(opts.PoolTTL),
    52  		pool.Transport(opts.Transport),
    53  	)
    54  
    55  	rc := &rpcClient{
    56  		opts: opts,
    57  		pool: p,
    58  		seq:  0,
    59  	}
    60  	rc.once.Store(false)
    61  
    62  	c := client.Client(rc)
    63  
    64  	// wrap in reverse
    65  	for i := len(opts.Wrappers); i > 0; i-- {
    66  		c = opts.Wrappers[i-1](c)
    67  	}
    68  
    69  	return c
    70  }
    71  
    72  func (r *rpcClient) newCodec(contentType string) (codec.NewCodec, error) {
    73  	if c, ok := r.opts.Codecs[contentType]; ok {
    74  		return c, nil
    75  	}
    76  	if cf, ok := DefaultCodecs[contentType]; ok {
    77  		return cf, nil
    78  	}
    79  	return nil, fmt.Errorf("Unsupported Content-Type: %s", contentType)
    80  }
    81  
    82  func (r *rpcClient) call(ctx context.Context, addr string, req client.Request, resp interface{}, opts client.CallOptions) error {
    83  	msg := &transport.Message{
    84  		Header: make(map[string]string),
    85  	}
    86  
    87  	md, ok := metadata.FromContext(ctx)
    88  	if ok {
    89  		for k, v := range md {
    90  			// don't copy Micro-Topic header, that used for pub/sub
    91  			// this fix case then client uses the same context that received in subscriber
    92  			if k == "Micro-Topic" {
    93  				continue
    94  			}
    95  			msg.Header[k] = v
    96  		}
    97  	}
    98  
    99  	// set timeout in nanoseconds
   100  	msg.Header["Timeout"] = fmt.Sprintf("%d", opts.RequestTimeout)
   101  	// set the content type for the request
   102  	msg.Header["Content-Type"] = req.ContentType()
   103  	// set the accept header
   104  	msg.Header["Accept"] = req.ContentType()
   105  
   106  	cf, err := r.newCodec(req.ContentType())
   107  	if err != nil {
   108  		return errors.InternalServerError("go.micro.client", err.Error())
   109  	}
   110  
   111  	dOpts := []transport.DialOption{
   112  		transport.WithStream(),
   113  	}
   114  
   115  	if opts.DialTimeout >= 0 {
   116  		dOpts = append(dOpts, transport.WithTimeout(opts.DialTimeout))
   117  	}
   118  
   119  	c, err := r.pool.Get(addr, dOpts...)
   120  	if err != nil {
   121  		return errors.InternalServerError("go.micro.client", "connection error: %v", err)
   122  	}
   123  
   124  	seq := atomic.AddUint64(&r.seq, 1) - 1
   125  	codec := newRpcCodec(msg, c, cf, "")
   126  
   127  	rsp := &rpcResponse{
   128  		socket: c,
   129  		codec:  codec,
   130  	}
   131  
   132  	stream := &rpcStream{
   133  		id:       fmt.Sprintf("%v", seq),
   134  		context:  ctx,
   135  		request:  req,
   136  		response: rsp,
   137  		codec:    codec,
   138  		closed:   make(chan bool),
   139  		release:  func(err error) { r.pool.Release(c, err) },
   140  		sendEOS:  false,
   141  	}
   142  	// close the stream on exiting this function
   143  	defer stream.Close()
   144  
   145  	// wait for error response
   146  	ch := make(chan error, 1)
   147  
   148  	go func() {
   149  		defer func() {
   150  			if r := recover(); r != nil {
   151  				ch <- errors.InternalServerError("go.micro.client", "panic recovered: %v", r)
   152  			}
   153  		}()
   154  
   155  		// send request
   156  		if err := stream.Send(req.Body()); err != nil {
   157  			ch <- err
   158  			return
   159  		}
   160  
   161  		// recv request
   162  		if err := stream.Recv(resp); err != nil {
   163  			ch <- err
   164  			return
   165  		}
   166  
   167  		// success
   168  		ch <- nil
   169  	}()
   170  
   171  	var grr error
   172  
   173  	select {
   174  	case err := <-ch:
   175  		return err
   176  	case <-ctx.Done():
   177  		grr = errors.Timeout("go.micro.client", fmt.Sprintf("%v", ctx.Err()))
   178  	}
   179  
   180  	// set the stream error
   181  	if grr != nil {
   182  		stream.Lock()
   183  		stream.err = grr
   184  		stream.Unlock()
   185  
   186  		return grr
   187  	}
   188  
   189  	return nil
   190  }
   191  
   192  func (r *rpcClient) stream(ctx context.Context, addr string, req client.Request, opts client.CallOptions) (client.Stream, error) {
   193  	msg := &transport.Message{
   194  		Header: make(map[string]string),
   195  	}
   196  
   197  	md, ok := metadata.FromContext(ctx)
   198  	if ok {
   199  		for k, v := range md {
   200  			msg.Header[k] = v
   201  		}
   202  	}
   203  
   204  	// set timeout in nanoseconds
   205  	if opts.StreamTimeout > time.Duration(0) {
   206  		msg.Header["Timeout"] = fmt.Sprintf("%d", opts.StreamTimeout)
   207  	}
   208  	// set the content type for the request
   209  	msg.Header["Content-Type"] = req.ContentType()
   210  	// set the accept header
   211  	msg.Header["Accept"] = req.ContentType()
   212  
   213  	cf, err := r.newCodec(req.ContentType())
   214  	if err != nil {
   215  		return nil, errors.InternalServerError("go.micro.client", err.Error())
   216  	}
   217  
   218  	dOpts := []transport.DialOption{
   219  		transport.WithStream(),
   220  	}
   221  
   222  	if opts.DialTimeout >= 0 {
   223  		dOpts = append(dOpts, transport.WithTimeout(opts.DialTimeout))
   224  	}
   225  
   226  	c, err := r.opts.Transport.Dial(addr, dOpts...)
   227  	if err != nil {
   228  		return nil, errors.InternalServerError("go.micro.client", "connection error: %v", err)
   229  	}
   230  
   231  	// increment the sequence number
   232  	seq := atomic.AddUint64(&r.seq, 1) - 1
   233  	id := fmt.Sprintf("%v", seq)
   234  
   235  	// create codec with stream id
   236  	codec := newRpcCodec(msg, c, cf, id)
   237  
   238  	rsp := &rpcResponse{
   239  		socket: c,
   240  		codec:  codec,
   241  	}
   242  
   243  	// set request codec
   244  	if r, ok := req.(*rpcRequest); ok {
   245  		r.codec = codec
   246  	}
   247  
   248  	stream := &rpcStream{
   249  		id:       id,
   250  		context:  ctx,
   251  		request:  req,
   252  		response: rsp,
   253  		codec:    codec,
   254  		// used to close the stream
   255  		closed: make(chan bool),
   256  		// signal the end of stream,
   257  		sendEOS: true,
   258  		// release func
   259  		release: func(err error) { c.Close() },
   260  	}
   261  
   262  	// wait for error response
   263  	ch := make(chan error, 1)
   264  
   265  	go func() {
   266  		// send the first message
   267  		ch <- stream.Send(req.Body())
   268  	}()
   269  
   270  	var grr error
   271  
   272  	select {
   273  	case err := <-ch:
   274  		grr = err
   275  	case <-ctx.Done():
   276  		grr = errors.Timeout("go.micro.client", fmt.Sprintf("%v", ctx.Err()))
   277  	}
   278  
   279  	if grr != nil {
   280  		// set the error
   281  		stream.Lock()
   282  		stream.err = grr
   283  		stream.Unlock()
   284  
   285  		// close the stream
   286  		stream.Close()
   287  		return nil, grr
   288  	}
   289  
   290  	return stream, nil
   291  }
   292  
   293  func (r *rpcClient) Init(opts ...client.Option) error {
   294  	size := r.opts.PoolSize
   295  	ttl := r.opts.PoolTTL
   296  	tr := r.opts.Transport
   297  
   298  	for _, o := range opts {
   299  		o(&r.opts)
   300  	}
   301  
   302  	// update pool configuration if the options changed
   303  	if size != r.opts.PoolSize || ttl != r.opts.PoolTTL || tr != r.opts.Transport {
   304  		// close existing pool
   305  		r.pool.Close()
   306  		// create new pool
   307  		r.pool = pool.NewPool(
   308  			pool.Size(r.opts.PoolSize),
   309  			pool.TTL(r.opts.PoolTTL),
   310  			pool.Transport(r.opts.Transport),
   311  		)
   312  	}
   313  
   314  	return nil
   315  }
   316  
   317  func (r *rpcClient) Options() client.Options {
   318  	return r.opts
   319  }
   320  
   321  func (r *rpcClient) Call(ctx context.Context, request client.Request, response interface{}, opts ...client.CallOption) error {
   322  	// make a copy of call opts
   323  	callOpts := r.opts.CallOptions
   324  	for _, opt := range opts {
   325  		opt(&callOpts)
   326  	}
   327  
   328  	// check if we already have a deadline
   329  	if d, ok := ctx.Deadline(); !ok {
   330  		// no deadline so we create a new one
   331  		var cancel context.CancelFunc
   332  		ctx, cancel = context.WithTimeout(ctx, callOpts.RequestTimeout)
   333  		defer cancel()
   334  	} else {
   335  		// got a deadline so no need to setup context
   336  		// but we need to set the timeout we pass along
   337  		remaining := d.Sub(time.Now())
   338  		client.WithRequestTimeout(remaining)(&callOpts)
   339  	}
   340  
   341  	// should we noop right here?
   342  	select {
   343  	case <-ctx.Done():
   344  		return errors.Timeout("go.micro.client", fmt.Sprintf("%v", ctx.Err()))
   345  	default:
   346  	}
   347  
   348  	// make copy of call method
   349  	rcall := r.call
   350  
   351  	// wrap the call in reverse
   352  	for i := len(callOpts.CallWrappers); i > 0; i-- {
   353  		rcall = callOpts.CallWrappers[i-1](rcall)
   354  	}
   355  
   356  	// use the router passed as a call option, or fallback to the rpc clients router
   357  	if callOpts.Router == nil {
   358  		callOpts.Router = r.opts.Router
   359  	}
   360  
   361  	if callOpts.Selector == nil {
   362  		callOpts.Selector = r.opts.Selector
   363  	}
   364  
   365  	// inject proxy address
   366  	// TODO: don't even bother using Lookup/Select in this case
   367  	if len(r.opts.Proxy) > 0 {
   368  		callOpts.Address = []string{r.opts.Proxy}
   369  	}
   370  
   371  	// lookup the route to send the reques to
   372  	// TODO apply any filtering here
   373  	routes, err := r.opts.Lookup(ctx, request, callOpts)
   374  	if err != nil {
   375  		return errors.InternalServerError("go.micro.client", err.Error())
   376  	}
   377  
   378  	// balance the list of nodes
   379  	next, err := callOpts.Selector.Select(routes)
   380  	if err != nil {
   381  		return err
   382  	}
   383  
   384  	// return errors.New("go.micro.client", "request timeout", 408)
   385  	call := func(i int) error {
   386  		// call backoff first. Someone may want an initial start delay
   387  		t, err := callOpts.Backoff(ctx, request, i)
   388  		if err != nil {
   389  			return errors.InternalServerError("go.micro.client", "backoff error: %v", err.Error())
   390  		}
   391  
   392  		// only sleep if greater than 0
   393  		if t.Seconds() > 0 {
   394  			time.Sleep(t)
   395  		}
   396  
   397  		// get the next node
   398  		node := next()
   399  
   400  		// make the call
   401  		err = rcall(ctx, node, request, response, callOpts)
   402  
   403  		// record the result of the call to inform future routing decisions
   404  		r.opts.Selector.Record(node, err)
   405  
   406  		return err
   407  	}
   408  
   409  	// get the retries
   410  	retries := callOpts.Retries
   411  
   412  	// disable retries when using a proxy
   413  	if len(r.opts.Proxy) > 0 {
   414  		retries = 0
   415  	}
   416  
   417  	ch := make(chan error, retries+1)
   418  	var gerr error
   419  
   420  	for i := 0; i <= retries; i++ {
   421  		go func(i int) {
   422  			ch <- call(i)
   423  		}(i)
   424  
   425  		select {
   426  		case <-ctx.Done():
   427  			return errors.Timeout("go.micro.client", fmt.Sprintf("call timeout: %v", ctx.Err()))
   428  		case err := <-ch:
   429  			// if the call succeeded lets bail early
   430  			if err == nil {
   431  				return nil
   432  			}
   433  
   434  			retry, rerr := callOpts.Retry(ctx, request, i, err)
   435  			if rerr != nil {
   436  				return rerr
   437  			}
   438  
   439  			if !retry {
   440  				return err
   441  			}
   442  
   443  			gerr = err
   444  		}
   445  	}
   446  
   447  	return gerr
   448  }
   449  
   450  func (r *rpcClient) Stream(ctx context.Context, request client.Request, opts ...client.CallOption) (client.Stream, error) {
   451  	// make a copy of call opts
   452  	callOpts := r.opts.CallOptions
   453  	for _, opt := range opts {
   454  		opt(&callOpts)
   455  	}
   456  
   457  	// should we noop right here?
   458  	select {
   459  	case <-ctx.Done():
   460  		return nil, errors.Timeout("go.micro.client", fmt.Sprintf("%v", ctx.Err()))
   461  	default:
   462  	}
   463  
   464  	// use the router passed as a call option, or fallback to the rpc clients router
   465  	if callOpts.Router == nil {
   466  		callOpts.Router = r.opts.Router
   467  	}
   468  
   469  	if callOpts.Selector == nil {
   470  		callOpts.Selector = r.opts.Selector
   471  	}
   472  
   473  	// inject proxy address
   474  	// TODO: don't even bother using Lookup/Select in this case
   475  	if len(r.opts.Proxy) > 0 {
   476  		callOpts.Address = []string{r.opts.Proxy}
   477  	}
   478  
   479  	// lookup the route to send the reques to
   480  	// TODO apply any filtering here
   481  	routes, err := r.opts.Lookup(ctx, request, callOpts)
   482  	if err != nil {
   483  		return nil, errors.InternalServerError("go.micro.client", err.Error())
   484  	}
   485  
   486  	// balance the list of nodes
   487  	next, err := callOpts.Selector.Select(routes)
   488  	if err != nil {
   489  		return nil, err
   490  	}
   491  
   492  	call := func(i int) (client.Stream, error) {
   493  		// call backoff first. Someone may want an initial start delay
   494  		t, err := callOpts.Backoff(ctx, request, i)
   495  		if err != nil {
   496  			return nil, errors.InternalServerError("go.micro.client", "backoff error: %v", err.Error())
   497  		}
   498  
   499  		// only sleep if greater than 0
   500  		if t.Seconds() > 0 {
   501  			time.Sleep(t)
   502  		}
   503  
   504  		// get the next node
   505  		node := next()
   506  
   507  		// perform the call
   508  		stream, err := r.stream(ctx, node, request, callOpts)
   509  
   510  		// record the result of the call to inform future routing decisions
   511  		r.opts.Selector.Record(node, err)
   512  
   513  		return stream, err
   514  	}
   515  
   516  	type response struct {
   517  		stream client.Stream
   518  		err    error
   519  	}
   520  
   521  	// get the retries
   522  	retries := callOpts.Retries
   523  
   524  	// disable retries when using a proxy
   525  	if len(r.opts.Proxy) > 0 {
   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 client.Message, opts ...client.PublishOption) error {
   564  	options := client.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 ...client.MessageOption) client.Message {
   633  	return newMessage(topic, message, r.opts.ContentType, opts...)
   634  }
   635  
   636  func (r *rpcClient) NewRequest(service, method string, request interface{}, reqOpts ...client.RequestOption) client.Request {
   637  	return newRequest(service, method, request, r.opts.ContentType, reqOpts...)
   638  }
   639  
   640  func (r *rpcClient) String() string {
   641  	return "mucp"
   642  }