github.com/tickoalcantara12/micro/v3@v3.0.0-20221007104245-9d75b9bcbab9/service/client/grpc/grpc.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/grpc/grpc.go
    16  
    17  // Package grpc provides a gRPC client
    18  package grpc
    19  
    20  import (
    21  	"context"
    22  	"crypto/tls"
    23  	"fmt"
    24  	"net"
    25  	"reflect"
    26  	"strings"
    27  	"sync/atomic"
    28  	"time"
    29  
    30  	"github.com/tickoalcantara12/micro/v3/service/broker"
    31  	"github.com/tickoalcantara12/micro/v3/service/client"
    32  	"github.com/tickoalcantara12/micro/v3/service/context/metadata"
    33  	"github.com/tickoalcantara12/micro/v3/service/errors"
    34  	raw "github.com/tickoalcantara12/micro/v3/util/codec/bytes"
    35  
    36  	"google.golang.org/grpc"
    37  	"google.golang.org/grpc/credentials"
    38  	"google.golang.org/grpc/encoding"
    39  	gmetadata "google.golang.org/grpc/metadata"
    40  )
    41  
    42  type grpcClient struct {
    43  	opts client.Options
    44  	pool *pool
    45  	once atomic.Value
    46  }
    47  
    48  func init() {
    49  	encoding.RegisterCodec(wrapCodec{jsonCodec{}})
    50  	encoding.RegisterCodec(wrapCodec{protoCodec{}})
    51  	encoding.RegisterCodec(wrapCodec{bytesCodec{}})
    52  }
    53  
    54  // secure returns the dial option for whether its a secure or insecure connection
    55  func (g *grpcClient) secure(addr string) grpc.DialOption {
    56  	// first we check if theres'a  tls config
    57  	if g.opts.Context != nil {
    58  		if v := g.opts.Context.Value(tlsAuth{}); v != nil {
    59  			tls := v.(*tls.Config)
    60  			creds := credentials.NewTLS(tls)
    61  			// return tls config if it exists
    62  			return grpc.WithTransportCredentials(creds)
    63  		}
    64  	}
    65  
    66  	// default config
    67  	tlsConfig := &tls.Config{}
    68  	defaultCreds := grpc.WithTransportCredentials(credentials.NewTLS(tlsConfig))
    69  
    70  	// check if the address is prepended with https
    71  	if strings.HasPrefix(addr, "https://") {
    72  		return defaultCreds
    73  	}
    74  
    75  	// if no port is specified or port is 443 default to tls
    76  	_, port, err := net.SplitHostPort(addr)
    77  	// assuming with no port its going to be secured
    78  	if port == "443" {
    79  		return defaultCreds
    80  	} else if err != nil && strings.Contains(err.Error(), "missing port in address") {
    81  		return defaultCreds
    82  	}
    83  
    84  	// other fallback to insecure
    85  	return grpc.WithInsecure()
    86  }
    87  
    88  func (g *grpcClient) call(ctx context.Context, addr string, req client.Request, rsp interface{}, opts client.CallOptions) error {
    89  	var header map[string]string
    90  
    91  	header = make(map[string]string)
    92  	if md, ok := metadata.FromContext(ctx); ok {
    93  		header = make(map[string]string, len(md))
    94  		for k, v := range md {
    95  			header[strings.ToLower(k)] = v
    96  		}
    97  	} else {
    98  		header = make(map[string]string)
    99  	}
   100  
   101  	// set timeout in nanoseconds
   102  	header["timeout"] = fmt.Sprintf("%d", opts.RequestTimeout)
   103  	// set the content type for the request
   104  	header["x-content-type"] = req.ContentType()
   105  
   106  	md := gmetadata.New(header)
   107  	ctx = gmetadata.NewOutgoingContext(ctx, md)
   108  
   109  	cf, err := g.newGRPCCodec(req.ContentType())
   110  	if err != nil {
   111  		return errors.InternalServerError("go.micro.client", err.Error())
   112  	}
   113  
   114  	maxRecvMsgSize := g.maxRecvMsgSizeValue()
   115  	maxSendMsgSize := g.maxSendMsgSizeValue()
   116  
   117  	var grr error
   118  
   119  	grpcDialOptions := []grpc.DialOption{
   120  		grpc.WithTimeout(opts.DialTimeout),
   121  		g.secure(addr),
   122  		grpc.WithDefaultCallOptions(
   123  			grpc.MaxCallRecvMsgSize(maxRecvMsgSize),
   124  			grpc.MaxCallSendMsgSize(maxSendMsgSize),
   125  		),
   126  	}
   127  
   128  	if opts := g.getGrpcDialOptions(); opts != nil {
   129  		grpcDialOptions = append(grpcDialOptions, opts...)
   130  	}
   131  
   132  	cc, err := g.pool.getConn(addr, grpcDialOptions...)
   133  	if err != nil {
   134  		return errors.InternalServerError("go.micro.client", fmt.Sprintf("Error sending request: %v", err))
   135  	}
   136  	defer func() {
   137  		// defer execution of release
   138  		g.pool.release(addr, cc, grr)
   139  	}()
   140  
   141  	ch := make(chan error, 1)
   142  
   143  	go func() {
   144  		grpcCallOptions := []grpc.CallOption{
   145  			grpc.ForceCodec(cf),
   146  			grpc.CallContentSubtype(cf.Name())}
   147  		if opts := g.getGrpcCallOptions(); opts != nil {
   148  			grpcCallOptions = append(grpcCallOptions, opts...)
   149  		}
   150  		err := cc.Invoke(ctx, methodToGRPC(req.Service(), req.Endpoint()), req.Body(), rsp, grpcCallOptions...)
   151  		ch <- microError(err)
   152  	}()
   153  
   154  	select {
   155  	case err := <-ch:
   156  		grr = err
   157  	case <-ctx.Done():
   158  		grr = errors.Timeout("go.micro.client", "%v", ctx.Err())
   159  	}
   160  
   161  	return grr
   162  }
   163  
   164  func (g *grpcClient) stream(ctx context.Context, addr string, req client.Request, rsp interface{}, opts client.CallOptions) error {
   165  	var header map[string]string
   166  
   167  	if md, ok := metadata.FromContext(ctx); ok {
   168  		header = make(map[string]string, len(md))
   169  		for k, v := range md {
   170  			header[k] = v
   171  		}
   172  	} else {
   173  		header = make(map[string]string)
   174  	}
   175  
   176  	// set timeout in nanoseconds
   177  	if opts.StreamTimeout > time.Duration(0) {
   178  		header["timeout"] = fmt.Sprintf("%d", opts.StreamTimeout)
   179  	}
   180  	// set the content type for the request
   181  	header["x-content-type"] = req.ContentType()
   182  
   183  	md := gmetadata.New(header)
   184  	ctx = gmetadata.NewOutgoingContext(ctx, md)
   185  
   186  	cf, err := g.newGRPCCodec(req.ContentType())
   187  	if err != nil {
   188  		return errors.InternalServerError("go.micro.client", err.Error())
   189  	}
   190  
   191  	wc := wrapCodec{cf}
   192  
   193  	maxRecvMsgSize := g.maxRecvMsgSizeValue()
   194  	maxSendMsgSize := g.maxSendMsgSizeValue()
   195  
   196  	grpcDialOptions := []grpc.DialOption{
   197  		grpc.WithTimeout(opts.DialTimeout),
   198  		g.secure(addr),
   199  		grpc.WithDefaultCallOptions(
   200  			grpc.MaxCallRecvMsgSize(maxRecvMsgSize),
   201  			grpc.MaxCallSendMsgSize(maxSendMsgSize),
   202  		),
   203  	}
   204  
   205  	if opts := g.getGrpcDialOptions(); opts != nil {
   206  		grpcDialOptions = append(grpcDialOptions, opts...)
   207  	}
   208  
   209  	cc, err := g.pool.getConn(addr, grpcDialOptions...)
   210  	if err != nil {
   211  		return errors.InternalServerError("go.micro.client", fmt.Sprintf("Error sending request: %v", err))
   212  	}
   213  
   214  	desc := &grpc.StreamDesc{
   215  		StreamName:    req.Service() + req.Endpoint(),
   216  		ClientStreams: true,
   217  		ServerStreams: true,
   218  	}
   219  
   220  	grpcCallOptions := []grpc.CallOption{
   221  		grpc.ForceCodec(wc),
   222  		grpc.CallContentSubtype(cf.Name()),
   223  	}
   224  	if opts := g.getGrpcCallOptions(); opts != nil {
   225  		grpcCallOptions = append(grpcCallOptions, opts...)
   226  	}
   227  
   228  	var cancel context.CancelFunc
   229  	ctx, cancel = context.WithCancel(ctx)
   230  
   231  	st, err := cc.NewStream(ctx, desc, methodToGRPC(req.Service(), req.Endpoint()), grpcCallOptions...)
   232  	if err != nil {
   233  		// we need to cleanup as we dialled and created a context
   234  		// cancel the context
   235  		cancel()
   236  		// release the connection
   237  		g.pool.release(addr, cc, err)
   238  		// now return the error
   239  		return errors.InternalServerError("go.micro.client", fmt.Sprintf("Error creating stream: %v", err))
   240  	}
   241  
   242  	codec := &grpcCodec{
   243  		s: st,
   244  		c: wc,
   245  	}
   246  
   247  	// set request codec
   248  	if r, ok := req.(*grpcRequest); ok {
   249  		r.codec = codec
   250  	}
   251  
   252  	// setup the stream response
   253  	stream := &grpcStream{
   254  		ClientStream: st,
   255  		context:      ctx,
   256  		request:      req,
   257  		response: &response{
   258  			conn:   cc,
   259  			stream: st,
   260  			codec:  cf,
   261  			gcodec: codec,
   262  		},
   263  		conn: cc,
   264  		close: func(err error) {
   265  			// cancel the context if an error occured
   266  			if err != nil {
   267  				cancel()
   268  			}
   269  
   270  			// defer execution of release
   271  			g.pool.release(addr, cc, err)
   272  		},
   273  	}
   274  
   275  	// set the stream as the response
   276  	val := reflect.ValueOf(rsp).Elem()
   277  	val.Set(reflect.ValueOf(stream).Elem())
   278  	return nil
   279  }
   280  
   281  func (g *grpcClient) poolMaxStreams() int {
   282  	if g.opts.Context == nil {
   283  		return DefaultPoolMaxStreams
   284  	}
   285  	v := g.opts.Context.Value(poolMaxStreams{})
   286  	if v == nil {
   287  		return DefaultPoolMaxStreams
   288  	}
   289  	return v.(int)
   290  }
   291  
   292  func (g *grpcClient) poolMaxIdle() int {
   293  	if g.opts.Context == nil {
   294  		return DefaultPoolMaxIdle
   295  	}
   296  	v := g.opts.Context.Value(poolMaxIdle{})
   297  	if v == nil {
   298  		return DefaultPoolMaxIdle
   299  	}
   300  	return v.(int)
   301  }
   302  
   303  func (g *grpcClient) maxRecvMsgSizeValue() int {
   304  	if g.opts.Context == nil {
   305  		return DefaultMaxRecvMsgSize
   306  	}
   307  	v := g.opts.Context.Value(maxRecvMsgSizeKey{})
   308  	if v == nil {
   309  		return DefaultMaxRecvMsgSize
   310  	}
   311  	return v.(int)
   312  }
   313  
   314  func (g *grpcClient) maxSendMsgSizeValue() int {
   315  	if g.opts.Context == nil {
   316  		return DefaultMaxSendMsgSize
   317  	}
   318  	v := g.opts.Context.Value(maxSendMsgSizeKey{})
   319  	if v == nil {
   320  		return DefaultMaxSendMsgSize
   321  	}
   322  	return v.(int)
   323  }
   324  
   325  func (g *grpcClient) newGRPCCodec(contentType string) (encoding.Codec, error) {
   326  	codecs := make(map[string]encoding.Codec)
   327  	if g.opts.Context != nil {
   328  		if v := g.opts.Context.Value(codecsKey{}); v != nil {
   329  			codecs = v.(map[string]encoding.Codec)
   330  		}
   331  	}
   332  	if c, ok := codecs[contentType]; ok {
   333  		return wrapCodec{c}, nil
   334  	}
   335  	if c, ok := defaultGRPCCodecs[contentType]; ok {
   336  		return wrapCodec{c}, nil
   337  	}
   338  	return nil, fmt.Errorf("Unsupported Content-Type: %s", contentType)
   339  }
   340  
   341  func (g *grpcClient) Init(opts ...client.Option) error {
   342  	size := g.opts.PoolSize
   343  	ttl := g.opts.PoolTTL
   344  
   345  	for _, o := range opts {
   346  		o(&g.opts)
   347  	}
   348  
   349  	// update pool configuration if the options changed
   350  	if size != g.opts.PoolSize || ttl != g.opts.PoolTTL {
   351  		g.pool.Lock()
   352  		g.pool.size = g.opts.PoolSize
   353  		g.pool.ttl = int64(g.opts.PoolTTL.Seconds())
   354  		g.pool.Unlock()
   355  	}
   356  
   357  	return nil
   358  }
   359  
   360  func (g *grpcClient) Options() client.Options {
   361  	return g.opts
   362  }
   363  
   364  func (g *grpcClient) NewMessage(topic string, msg interface{}, opts ...client.MessageOption) client.Message {
   365  	return newGRPCEvent(topic, msg, g.opts.ContentType, opts...)
   366  }
   367  
   368  func (g *grpcClient) NewRequest(service, method string, req interface{}, reqOpts ...client.RequestOption) client.Request {
   369  	return newGRPCRequest(service, method, req, g.opts.ContentType, reqOpts...)
   370  }
   371  
   372  func (g *grpcClient) Call(ctx context.Context, req client.Request, rsp interface{}, opts ...client.CallOption) error {
   373  	if req == nil {
   374  		return errors.InternalServerError("go.micro.client", "req is nil")
   375  	} else if rsp == nil {
   376  		return errors.InternalServerError("go.micro.client", "rsp is nil")
   377  	}
   378  	// make a copy of call opts
   379  	callOpts := g.opts.CallOptions
   380  	for _, opt := range opts {
   381  		opt(&callOpts)
   382  	}
   383  
   384  	// check if we already have a deadline
   385  	d, ok := ctx.Deadline()
   386  	if !ok {
   387  		// no deadline so we create a new one
   388  		var cancel context.CancelFunc
   389  		ctx, cancel = context.WithTimeout(ctx, callOpts.RequestTimeout)
   390  		defer cancel()
   391  	} else {
   392  		// got a deadline so no need to setup context
   393  		// but we need to set the timeout we pass along
   394  		opt := client.WithRequestTimeout(time.Until(d))
   395  		opt(&callOpts)
   396  	}
   397  
   398  	// should we noop right here?
   399  	select {
   400  	case <-ctx.Done():
   401  		return errors.New("go.micro.client", fmt.Sprintf("%v", ctx.Err()), 408)
   402  	default:
   403  	}
   404  
   405  	// make copy of call method
   406  	gcall := g.call
   407  
   408  	// wrap the call in reverse
   409  	for i := len(callOpts.CallWrappers); i > 0; i-- {
   410  		gcall = callOpts.CallWrappers[i-1](gcall)
   411  	}
   412  
   413  	// use the router passed as a call option, or fallback to the rpc clients router
   414  	if callOpts.Router == nil {
   415  		callOpts.Router = g.opts.Router
   416  	}
   417  
   418  	if callOpts.Selector == nil {
   419  		callOpts.Selector = g.opts.Selector
   420  	}
   421  
   422  	// inject proxy address if no address is specified
   423  	// TODO: don't even bother using Lookup/Select in this case
   424  	if len(g.opts.Proxy) > 0 {
   425  		callOpts.Address = append(callOpts.Address, g.opts.Proxy)
   426  	}
   427  
   428  	// lookup the route to send the reques to
   429  	// TODO apply any filtering here
   430  	routes, err := g.opts.Lookup(ctx, req, callOpts)
   431  	if err != nil {
   432  		return errors.InternalServerError("go.micro.client", err.Error())
   433  	}
   434  
   435  	// balance the list of nodes
   436  	next, err := callOpts.Selector.Select(routes)
   437  	if err != nil {
   438  		return err
   439  	}
   440  
   441  	// return errors.New("go.micro.client", "request timeout", 408)
   442  	call := func(i int) error {
   443  		// call backoff first. Someone may want an initial start delay
   444  		t, err := callOpts.Backoff(ctx, req, i)
   445  		if err != nil {
   446  			return errors.InternalServerError("go.micro.client", err.Error())
   447  		}
   448  
   449  		// only sleep if greater than 0
   450  		if t.Seconds() > 0 {
   451  			time.Sleep(t)
   452  		}
   453  
   454  		// get the next node
   455  		node := next()
   456  
   457  		// make the call
   458  		err = gcall(ctx, node, req, rsp, callOpts)
   459  
   460  		// record the result of the call to inform future routing decisions
   461  		g.opts.Selector.Record(node, err)
   462  
   463  		// try and transform the error to a go-micro error
   464  		if verr, ok := err.(*errors.Error); ok {
   465  			return verr
   466  		}
   467  
   468  		return err
   469  	}
   470  
   471  	ch := make(chan error, callOpts.Retries+1)
   472  	var gerr error
   473  
   474  	for i := 0; i <= callOpts.Retries; i++ {
   475  		go func(i int) {
   476  			ch <- call(i)
   477  		}(i)
   478  
   479  		select {
   480  		case <-ctx.Done():
   481  			return errors.New("go.micro.client", fmt.Sprintf("%v", ctx.Err()), 408)
   482  		case err := <-ch:
   483  			// if the call succeeded lets bail early
   484  			if err == nil {
   485  				return nil
   486  			}
   487  
   488  			retry, rerr := callOpts.Retry(ctx, req, i, err)
   489  			if rerr != nil {
   490  				return rerr
   491  			}
   492  
   493  			if !retry {
   494  				return err
   495  			}
   496  
   497  			gerr = err
   498  		}
   499  	}
   500  
   501  	return gerr
   502  }
   503  
   504  func (g *grpcClient) Stream(ctx context.Context, req client.Request, opts ...client.CallOption) (client.Stream, error) {
   505  	// make a copy of call opts
   506  	callOpts := g.opts.CallOptions
   507  	for _, opt := range opts {
   508  		opt(&callOpts)
   509  	}
   510  
   511  	// #200 - streams shouldn't have a request timeout set on the context
   512  
   513  	// should we noop right here?
   514  	select {
   515  	case <-ctx.Done():
   516  		return nil, errors.New("go.micro.client", fmt.Sprintf("%v", ctx.Err()), 408)
   517  	default:
   518  	}
   519  
   520  	// make a copy of stream
   521  	gstream := g.stream
   522  
   523  	// wrap the call in reverse
   524  	for i := len(callOpts.CallWrappers); i > 0; i-- {
   525  		gstream = callOpts.CallWrappers[i-1](gstream)
   526  	}
   527  
   528  	// use the router passed as a call option, or fallback to the rpc clients router
   529  	if callOpts.Router == nil {
   530  		callOpts.Router = g.opts.Router
   531  	}
   532  
   533  	if callOpts.Selector == nil {
   534  		callOpts.Selector = g.opts.Selector
   535  	}
   536  
   537  	// inject proxy address
   538  	// TODO: don't even bother using Lookup/Select in this case
   539  	if len(g.opts.Proxy) > 0 {
   540  		callOpts.Address = []string{g.opts.Proxy}
   541  	}
   542  
   543  	// lookup the route to send the reques to
   544  	// TODO: move to internal lookup func
   545  	routes, err := g.opts.Lookup(ctx, req, callOpts)
   546  	if err != nil {
   547  		return nil, errors.InternalServerError("go.micro.client", err.Error())
   548  	}
   549  
   550  	// balance the list of nodes
   551  	next, err := callOpts.Selector.Select(routes)
   552  	if err != nil {
   553  		return nil, err
   554  	}
   555  
   556  	call := func(i int) (client.Stream, error) {
   557  		// call backoff first. Someone may want an initial start delay
   558  		t, err := callOpts.Backoff(ctx, req, i)
   559  		if err != nil {
   560  			return nil, errors.InternalServerError("go.micro.client", err.Error())
   561  		}
   562  
   563  		// only sleep if greater than 0
   564  		if t.Seconds() > 0 {
   565  			time.Sleep(t)
   566  		}
   567  
   568  		// get the next node
   569  		node := next()
   570  
   571  		// make the call
   572  		stream := &grpcStream{}
   573  		err = g.stream(ctx, node, req, stream, callOpts)
   574  
   575  		// record the result of the call to inform future routing decisions
   576  		g.opts.Selector.Record(node, err)
   577  
   578  		// try and transform the error to a go-micro error
   579  		if verr, ok := err.(*errors.Error); ok {
   580  			return nil, verr
   581  		}
   582  
   583  		g.opts.Selector.Record(node, err)
   584  		return stream, err
   585  	}
   586  
   587  	type response struct {
   588  		stream client.Stream
   589  		err    error
   590  	}
   591  
   592  	ch := make(chan response, callOpts.Retries+1)
   593  	var grr error
   594  
   595  	for i := 0; i <= callOpts.Retries; i++ {
   596  		go func(i int) {
   597  			s, err := call(i)
   598  			ch <- response{s, err}
   599  		}(i)
   600  
   601  		select {
   602  		case <-ctx.Done():
   603  			return nil, errors.New("go.micro.client", fmt.Sprintf("%v", ctx.Err()), 408)
   604  		case rsp := <-ch:
   605  			// if the call succeeded lets bail early
   606  			if rsp.err == nil {
   607  				return rsp.stream, nil
   608  			}
   609  
   610  			retry, rerr := callOpts.Retry(ctx, req, i, grr)
   611  			if rerr != nil {
   612  				return nil, rerr
   613  			}
   614  
   615  			if !retry {
   616  				return nil, rsp.err
   617  			}
   618  
   619  			grr = rsp.err
   620  		}
   621  	}
   622  
   623  	return nil, grr
   624  }
   625  
   626  func (g *grpcClient) Publish(ctx context.Context, p client.Message, opts ...client.PublishOption) error {
   627  	var options client.PublishOptions
   628  	var body []byte
   629  
   630  	// fail early on connect error
   631  	if !g.once.Load().(bool) {
   632  		if err := g.opts.Broker.Connect(); err != nil {
   633  			return errors.InternalServerError("go.micro.client", err.Error())
   634  		}
   635  		g.once.Store(true)
   636  	}
   637  
   638  	for _, o := range opts {
   639  		o(&options)
   640  	}
   641  
   642  	md, ok := metadata.FromContext(ctx)
   643  	if !ok {
   644  		md = make(map[string]string)
   645  	}
   646  	md["Content-Type"] = p.ContentType()
   647  	md["Micro-Topic"] = p.Topic()
   648  
   649  	// passed in raw data
   650  	if d, ok := p.Payload().(*raw.Frame); ok {
   651  		body = d.Data
   652  	} else {
   653  		// use codec for payload
   654  		cf, err := g.newGRPCCodec(p.ContentType())
   655  		if err != nil {
   656  			return errors.InternalServerError("go.micro.client", err.Error())
   657  		}
   658  		// set the body
   659  		b, err := cf.Marshal(p.Payload())
   660  		if err != nil {
   661  			return errors.InternalServerError("go.micro.client", err.Error())
   662  		}
   663  		body = b
   664  	}
   665  
   666  	topic := p.Topic()
   667  
   668  	// get the exchange
   669  	if len(options.Exchange) > 0 {
   670  		topic = options.Exchange
   671  	}
   672  
   673  	return g.opts.Broker.Publish(topic, &broker.Message{
   674  		Header: md,
   675  		Body:   body,
   676  	}, broker.PublishContext(options.Context))
   677  }
   678  
   679  func (g *grpcClient) String() string {
   680  	return "grpc"
   681  }
   682  
   683  func (g *grpcClient) getGrpcDialOptions() []grpc.DialOption {
   684  	if g.opts.CallOptions.Context == nil {
   685  		return nil
   686  	}
   687  
   688  	v := g.opts.CallOptions.Context.Value(grpcDialOptions{})
   689  
   690  	if v == nil {
   691  		return nil
   692  	}
   693  
   694  	opts, ok := v.([]grpc.DialOption)
   695  
   696  	if !ok {
   697  		return nil
   698  	}
   699  
   700  	return opts
   701  }
   702  
   703  func (g *grpcClient) getGrpcCallOptions() []grpc.CallOption {
   704  	if g.opts.CallOptions.Context == nil {
   705  		return nil
   706  	}
   707  
   708  	v := g.opts.CallOptions.Context.Value(grpcCallOptions{})
   709  
   710  	if v == nil {
   711  		return nil
   712  	}
   713  
   714  	opts, ok := v.([]grpc.CallOption)
   715  
   716  	if !ok {
   717  		return nil
   718  	}
   719  
   720  	return opts
   721  }
   722  
   723  func newClient(opts ...client.Option) client.Client {
   724  	options := client.NewOptions()
   725  	// default content type for grpc
   726  	options.ContentType = "application/grpc+proto"
   727  
   728  	for _, o := range opts {
   729  		o(&options)
   730  	}
   731  
   732  	rc := &grpcClient{
   733  		opts: options,
   734  	}
   735  	rc.once.Store(false)
   736  
   737  	rc.pool = newPool(options.PoolSize, options.PoolTTL, rc.poolMaxIdle(), rc.poolMaxStreams())
   738  
   739  	c := client.Client(rc)
   740  
   741  	// wrap in reverse
   742  	for i := len(options.Wrappers); i > 0; i-- {
   743  		c = options.Wrappers[i-1](c)
   744  	}
   745  
   746  	return c
   747  }
   748  
   749  func NewClient(opts ...client.Option) client.Client {
   750  	return newClient(opts...)
   751  }