github.com/micro/go-micro/v2@v2.9.1/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/micro/go-micro/v2/broker" 15 "github.com/micro/go-micro/v2/client" 16 "github.com/micro/go-micro/v2/client/selector" 17 raw "github.com/micro/go-micro/v2/codec/bytes" 18 "github.com/micro/go-micro/v2/errors" 19 "github.com/micro/go-micro/v2/metadata" 20 "github.com/micro/go-micro/v2/registry" 21 pnet "github.com/micro/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 cancel: cancel, 282 } 283 284 // set the stream as the response 285 val := reflect.ValueOf(rsp).Elem() 286 val.Set(reflect.ValueOf(stream).Elem()) 287 return nil 288 } 289 290 func (g *grpcClient) poolMaxStreams() int { 291 if g.opts.Context == nil { 292 return DefaultPoolMaxStreams 293 } 294 v := g.opts.Context.Value(poolMaxStreams{}) 295 if v == nil { 296 return DefaultPoolMaxStreams 297 } 298 return v.(int) 299 } 300 301 func (g *grpcClient) poolMaxIdle() int { 302 if g.opts.Context == nil { 303 return DefaultPoolMaxIdle 304 } 305 v := g.opts.Context.Value(poolMaxIdle{}) 306 if v == nil { 307 return DefaultPoolMaxIdle 308 } 309 return v.(int) 310 } 311 312 func (g *grpcClient) maxRecvMsgSizeValue() int { 313 if g.opts.Context == nil { 314 return DefaultMaxRecvMsgSize 315 } 316 v := g.opts.Context.Value(maxRecvMsgSizeKey{}) 317 if v == nil { 318 return DefaultMaxRecvMsgSize 319 } 320 return v.(int) 321 } 322 323 func (g *grpcClient) maxSendMsgSizeValue() int { 324 if g.opts.Context == nil { 325 return DefaultMaxSendMsgSize 326 } 327 v := g.opts.Context.Value(maxSendMsgSizeKey{}) 328 if v == nil { 329 return DefaultMaxSendMsgSize 330 } 331 return v.(int) 332 } 333 334 func (g *grpcClient) newGRPCCodec(contentType string) (encoding.Codec, error) { 335 codecs := make(map[string]encoding.Codec) 336 if g.opts.Context != nil { 337 if v := g.opts.Context.Value(codecsKey{}); v != nil { 338 codecs = v.(map[string]encoding.Codec) 339 } 340 } 341 if c, ok := codecs[contentType]; ok { 342 return wrapCodec{c}, nil 343 } 344 if c, ok := defaultGRPCCodecs[contentType]; ok { 345 return wrapCodec{c}, nil 346 } 347 return nil, fmt.Errorf("Unsupported Content-Type: %s", contentType) 348 } 349 350 func (g *grpcClient) Init(opts ...client.Option) error { 351 size := g.opts.PoolSize 352 ttl := g.opts.PoolTTL 353 354 for _, o := range opts { 355 o(&g.opts) 356 } 357 358 // update pool configuration if the options changed 359 if size != g.opts.PoolSize || ttl != g.opts.PoolTTL { 360 g.pool.Lock() 361 g.pool.size = g.opts.PoolSize 362 g.pool.ttl = int64(g.opts.PoolTTL.Seconds()) 363 g.pool.Unlock() 364 } 365 366 return nil 367 } 368 369 func (g *grpcClient) Options() client.Options { 370 return g.opts 371 } 372 373 func (g *grpcClient) NewMessage(topic string, msg interface{}, opts ...client.MessageOption) client.Message { 374 return newGRPCEvent(topic, msg, g.opts.ContentType, opts...) 375 } 376 377 func (g *grpcClient) NewRequest(service, method string, req interface{}, reqOpts ...client.RequestOption) client.Request { 378 return newGRPCRequest(service, method, req, g.opts.ContentType, reqOpts...) 379 } 380 381 func (g *grpcClient) Call(ctx context.Context, req client.Request, rsp interface{}, opts ...client.CallOption) error { 382 if req == nil { 383 return errors.InternalServerError("go.micro.client", "req is nil") 384 } else if rsp == nil { 385 return errors.InternalServerError("go.micro.client", "rsp is nil") 386 } 387 // make a copy of call opts 388 callOpts := g.opts.CallOptions 389 for _, opt := range opts { 390 opt(&callOpts) 391 } 392 393 next, err := g.next(req, callOpts) 394 if err != nil { 395 return err 396 } 397 398 // check if we already have a deadline 399 d, ok := ctx.Deadline() 400 if !ok { 401 // no deadline so we create a new one 402 var cancel context.CancelFunc 403 ctx, cancel = context.WithTimeout(ctx, callOpts.RequestTimeout) 404 defer cancel() 405 } else { 406 // got a deadline so no need to setup context 407 // but we need to set the timeout we pass along 408 opt := client.WithRequestTimeout(time.Until(d)) 409 opt(&callOpts) 410 } 411 412 // should we noop right here? 413 select { 414 case <-ctx.Done(): 415 return errors.New("go.micro.client", fmt.Sprintf("%v", ctx.Err()), 408) 416 default: 417 } 418 419 // make copy of call method 420 gcall := g.call 421 422 // wrap the call in reverse 423 for i := len(callOpts.CallWrappers); i > 0; i-- { 424 gcall = callOpts.CallWrappers[i-1](gcall) 425 } 426 427 // return errors.New("go.micro.client", "request timeout", 408) 428 call := func(i int) error { 429 // call backoff first. Someone may want an initial start delay 430 t, err := callOpts.Backoff(ctx, req, i) 431 if err != nil { 432 return errors.InternalServerError("go.micro.client", err.Error()) 433 } 434 435 // only sleep if greater than 0 436 if t.Seconds() > 0 { 437 time.Sleep(t) 438 } 439 440 // select next node 441 node, err := next() 442 service := req.Service() 443 if err != nil { 444 if err == selector.ErrNotFound { 445 return errors.InternalServerError("go.micro.client", "service %s: %s", service, err.Error()) 446 } 447 return errors.InternalServerError("go.micro.client", "error selecting %s node: %s", service, err.Error()) 448 } 449 450 // make the call 451 err = gcall(ctx, node, req, rsp, callOpts) 452 g.opts.Selector.Mark(service, node, err) 453 if verr, ok := err.(*errors.Error); ok { 454 return verr 455 } 456 457 return err 458 } 459 460 ch := make(chan error, callOpts.Retries+1) 461 var gerr error 462 463 for i := 0; i <= callOpts.Retries; i++ { 464 go func(i int) { 465 ch <- call(i) 466 }(i) 467 468 select { 469 case <-ctx.Done(): 470 return errors.New("go.micro.client", fmt.Sprintf("%v", ctx.Err()), 408) 471 case err := <-ch: 472 // if the call succeeded lets bail early 473 if err == nil { 474 return nil 475 } 476 477 retry, rerr := callOpts.Retry(ctx, req, i, err) 478 if rerr != nil { 479 return rerr 480 } 481 482 if !retry { 483 return err 484 } 485 486 gerr = err 487 } 488 } 489 490 return gerr 491 } 492 493 func (g *grpcClient) Stream(ctx context.Context, req client.Request, opts ...client.CallOption) (client.Stream, error) { 494 // make a copy of call opts 495 callOpts := g.opts.CallOptions 496 for _, opt := range opts { 497 opt(&callOpts) 498 } 499 500 next, err := g.next(req, callOpts) 501 if err != nil { 502 return nil, err 503 } 504 505 // #200 - streams shouldn't have a request timeout set on the context 506 507 // should we noop right here? 508 select { 509 case <-ctx.Done(): 510 return nil, errors.New("go.micro.client", fmt.Sprintf("%v", ctx.Err()), 408) 511 default: 512 } 513 514 // make a copy of stream 515 gstream := g.stream 516 517 // wrap the call in reverse 518 for i := len(callOpts.CallWrappers); i > 0; i-- { 519 gstream = callOpts.CallWrappers[i-1](gstream) 520 } 521 522 call := func(i int) (client.Stream, error) { 523 // call backoff first. Someone may want an initial start delay 524 t, err := callOpts.Backoff(ctx, req, i) 525 if err != nil { 526 return nil, errors.InternalServerError("go.micro.client", err.Error()) 527 } 528 529 // only sleep if greater than 0 530 if t.Seconds() > 0 { 531 time.Sleep(t) 532 } 533 534 node, err := next() 535 service := req.Service() 536 if err != nil { 537 if err == selector.ErrNotFound { 538 return nil, errors.InternalServerError("go.micro.client", "service %s: %s", service, err.Error()) 539 } 540 return nil, errors.InternalServerError("go.micro.client", "error selecting %s node: %s", service, err.Error()) 541 } 542 543 // make the call 544 stream := &grpcStream{} 545 err = g.stream(ctx, node, req, stream, callOpts) 546 547 g.opts.Selector.Mark(service, node, err) 548 return stream, err 549 } 550 551 type response struct { 552 stream client.Stream 553 err error 554 } 555 556 ch := make(chan response, callOpts.Retries+1) 557 var grr error 558 559 for i := 0; i <= callOpts.Retries; i++ { 560 go func(i int) { 561 s, err := call(i) 562 ch <- response{s, err} 563 }(i) 564 565 select { 566 case <-ctx.Done(): 567 return nil, errors.New("go.micro.client", fmt.Sprintf("%v", ctx.Err()), 408) 568 case rsp := <-ch: 569 // if the call succeeded lets bail early 570 if rsp.err == nil { 571 return rsp.stream, nil 572 } 573 574 retry, rerr := callOpts.Retry(ctx, req, i, err) 575 if rerr != nil { 576 return nil, rerr 577 } 578 579 if !retry { 580 return nil, rsp.err 581 } 582 583 grr = rsp.err 584 } 585 } 586 587 return nil, grr 588 } 589 590 func (g *grpcClient) Publish(ctx context.Context, p client.Message, opts ...client.PublishOption) error { 591 var options client.PublishOptions 592 for _, o := range opts { 593 o(&options) 594 } 595 596 md, ok := metadata.FromContext(ctx) 597 if !ok { 598 md = make(map[string]string) 599 } 600 md["Content-Type"] = p.ContentType() 601 md["Micro-Topic"] = p.Topic() 602 603 cf, err := g.newGRPCCodec(p.ContentType()) 604 if err != nil { 605 return errors.InternalServerError("go.micro.client", err.Error()) 606 } 607 608 var body []byte 609 610 // passed in raw data 611 if d, ok := p.Payload().(*raw.Frame); ok { 612 body = d.Data 613 } else { 614 // set the body 615 b, err := cf.Marshal(p.Payload()) 616 if err != nil { 617 return errors.InternalServerError("go.micro.client", err.Error()) 618 } 619 body = b 620 } 621 622 if !g.once.Load().(bool) { 623 if err = g.opts.Broker.Connect(); err != nil { 624 return errors.InternalServerError("go.micro.client", err.Error()) 625 } 626 g.once.Store(true) 627 } 628 629 topic := p.Topic() 630 631 // get the exchange 632 if len(options.Exchange) > 0 { 633 topic = options.Exchange 634 } 635 636 return g.opts.Broker.Publish(topic, &broker.Message{ 637 Header: md, 638 Body: body, 639 }, broker.PublishContext(options.Context)) 640 } 641 642 func (g *grpcClient) String() string { 643 return "grpc" 644 } 645 646 func (g *grpcClient) getGrpcDialOptions() []grpc.DialOption { 647 if g.opts.CallOptions.Context == nil { 648 return nil 649 } 650 651 v := g.opts.CallOptions.Context.Value(grpcDialOptions{}) 652 653 if v == nil { 654 return nil 655 } 656 657 opts, ok := v.([]grpc.DialOption) 658 659 if !ok { 660 return nil 661 } 662 663 return opts 664 } 665 666 func (g *grpcClient) getGrpcCallOptions() []grpc.CallOption { 667 if g.opts.CallOptions.Context == nil { 668 return nil 669 } 670 671 v := g.opts.CallOptions.Context.Value(grpcCallOptions{}) 672 673 if v == nil { 674 return nil 675 } 676 677 opts, ok := v.([]grpc.CallOption) 678 679 if !ok { 680 return nil 681 } 682 683 return opts 684 } 685 686 func newClient(opts ...client.Option) client.Client { 687 options := client.NewOptions() 688 // default content type for grpc 689 options.ContentType = "application/grpc+proto" 690 691 for _, o := range opts { 692 o(&options) 693 } 694 695 rc := &grpcClient{ 696 opts: options, 697 } 698 rc.once.Store(false) 699 700 rc.pool = newPool(options.PoolSize, options.PoolTTL, rc.poolMaxIdle(), rc.poolMaxStreams()) 701 702 c := client.Client(rc) 703 704 // wrap in reverse 705 for i := len(options.Wrappers); i > 0; i-- { 706 c = options.Wrappers[i-1](c) 707 } 708 709 return c 710 } 711 712 func NewClient(opts ...client.Option) client.Client { 713 return newClient(opts...) 714 }