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