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