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 }