github.com/micro/go-micro/v2@v2.9.1/client/rpc_client.go (about) 1 package client 2 3 import ( 4 "context" 5 "fmt" 6 "sync/atomic" 7 "time" 8 9 "github.com/google/uuid" 10 "github.com/micro/go-micro/v2/broker" 11 "github.com/micro/go-micro/v2/client/selector" 12 "github.com/micro/go-micro/v2/codec" 13 raw "github.com/micro/go-micro/v2/codec/bytes" 14 "github.com/micro/go-micro/v2/errors" 15 "github.com/micro/go-micro/v2/metadata" 16 "github.com/micro/go-micro/v2/registry" 17 "github.com/micro/go-micro/v2/transport" 18 "github.com/micro/go-micro/v2/util/buf" 19 "github.com/micro/go-micro/v2/util/net" 20 "github.com/micro/go-micro/v2/util/pool" 21 ) 22 23 type rpcClient struct { 24 once atomic.Value 25 opts Options 26 pool pool.Pool 27 seq uint64 28 } 29 30 func newRpcClient(opt ...Option) Client { 31 opts := NewOptions(opt...) 32 33 p := pool.NewPool( 34 pool.Size(opts.PoolSize), 35 pool.TTL(opts.PoolTTL), 36 pool.Transport(opts.Transport), 37 ) 38 39 rc := &rpcClient{ 40 opts: opts, 41 pool: p, 42 seq: 0, 43 } 44 rc.once.Store(false) 45 46 c := Client(rc) 47 48 // wrap in reverse 49 for i := len(opts.Wrappers); i > 0; i-- { 50 c = opts.Wrappers[i-1](c) 51 } 52 53 return c 54 } 55 56 func (r *rpcClient) newCodec(contentType string) (codec.NewCodec, error) { 57 if c, ok := r.opts.Codecs[contentType]; ok { 58 return c, nil 59 } 60 if cf, ok := DefaultCodecs[contentType]; ok { 61 return cf, nil 62 } 63 return nil, fmt.Errorf("Unsupported Content-Type: %s", contentType) 64 } 65 66 func (r *rpcClient) call(ctx context.Context, node *registry.Node, req Request, resp interface{}, opts CallOptions) error { 67 address := node.Address 68 69 msg := &transport.Message{ 70 Header: make(map[string]string), 71 } 72 73 md, ok := metadata.FromContext(ctx) 74 if ok { 75 for k, v := range md { 76 // don't copy Micro-Topic header, that used for pub/sub 77 // this fix case then client uses the same context that received in subscriber 78 if k == "Micro-Topic" { 79 continue 80 } 81 msg.Header[k] = v 82 } 83 } 84 85 // set timeout in nanoseconds 86 msg.Header["Timeout"] = fmt.Sprintf("%d", opts.RequestTimeout) 87 // set the content type for the request 88 msg.Header["Content-Type"] = req.ContentType() 89 // set the accept header 90 msg.Header["Accept"] = req.ContentType() 91 92 // setup old protocol 93 cf := setupProtocol(msg, node) 94 95 // no codec specified 96 if cf == nil { 97 var err error 98 cf, err = r.newCodec(req.ContentType()) 99 if err != nil { 100 return errors.InternalServerError("go.micro.client", err.Error()) 101 } 102 } 103 104 dOpts := []transport.DialOption{ 105 transport.WithStream(), 106 } 107 108 if opts.DialTimeout >= 0 { 109 dOpts = append(dOpts, transport.WithTimeout(opts.DialTimeout)) 110 } 111 112 c, err := r.pool.Get(address, dOpts...) 113 if err != nil { 114 return errors.InternalServerError("go.micro.client", "connection error: %v", err) 115 } 116 117 seq := atomic.AddUint64(&r.seq, 1) - 1 118 codec := newRpcCodec(msg, c, cf, "") 119 120 rsp := &rpcResponse{ 121 socket: c, 122 codec: codec, 123 } 124 125 stream := &rpcStream{ 126 id: fmt.Sprintf("%v", seq), 127 context: ctx, 128 request: req, 129 response: rsp, 130 codec: codec, 131 closed: make(chan bool), 132 release: func(err error) { r.pool.Release(c, err) }, 133 sendEOS: false, 134 } 135 // close the stream on exiting this function 136 defer stream.Close() 137 138 // wait for error response 139 ch := make(chan error, 1) 140 141 go func() { 142 defer func() { 143 if r := recover(); r != nil { 144 ch <- errors.InternalServerError("go.micro.client", "panic recovered: %v", r) 145 } 146 }() 147 148 // send request 149 if err := stream.Send(req.Body()); err != nil { 150 ch <- err 151 return 152 } 153 154 // recv request 155 if err := stream.Recv(resp); err != nil { 156 ch <- err 157 return 158 } 159 160 // success 161 ch <- nil 162 }() 163 164 var grr error 165 166 select { 167 case err := <-ch: 168 return err 169 case <-ctx.Done(): 170 grr = errors.Timeout("go.micro.client", fmt.Sprintf("%v", ctx.Err())) 171 } 172 173 // set the stream error 174 if grr != nil { 175 stream.Lock() 176 stream.err = grr 177 stream.Unlock() 178 179 return grr 180 } 181 182 return nil 183 } 184 185 func (r *rpcClient) stream(ctx context.Context, node *registry.Node, req Request, opts CallOptions) (Stream, error) { 186 address := node.Address 187 188 msg := &transport.Message{ 189 Header: make(map[string]string), 190 } 191 192 md, ok := metadata.FromContext(ctx) 193 if ok { 194 for k, v := range md { 195 msg.Header[k] = v 196 } 197 } 198 199 // set timeout in nanoseconds 200 if opts.StreamTimeout > time.Duration(0) { 201 msg.Header["Timeout"] = fmt.Sprintf("%d", opts.StreamTimeout) 202 } 203 // set the content type for the request 204 msg.Header["Content-Type"] = req.ContentType() 205 // set the accept header 206 msg.Header["Accept"] = req.ContentType() 207 208 // set old codecs 209 cf := setupProtocol(msg, node) 210 211 // no codec specified 212 if cf == nil { 213 var err error 214 cf, err = r.newCodec(req.ContentType()) 215 if err != nil { 216 return nil, errors.InternalServerError("go.micro.client", err.Error()) 217 } 218 } 219 220 dOpts := []transport.DialOption{ 221 transport.WithStream(), 222 } 223 224 if opts.DialTimeout >= 0 { 225 dOpts = append(dOpts, transport.WithTimeout(opts.DialTimeout)) 226 } 227 228 c, err := r.opts.Transport.Dial(address, dOpts...) 229 if err != nil { 230 return nil, errors.InternalServerError("go.micro.client", "connection error: %v", err) 231 } 232 233 // increment the sequence number 234 seq := atomic.AddUint64(&r.seq, 1) - 1 235 id := fmt.Sprintf("%v", seq) 236 237 // create codec with stream id 238 codec := newRpcCodec(msg, c, cf, id) 239 240 rsp := &rpcResponse{ 241 socket: c, 242 codec: codec, 243 } 244 245 // set request codec 246 if r, ok := req.(*rpcRequest); ok { 247 r.codec = codec 248 } 249 250 stream := &rpcStream{ 251 id: id, 252 context: ctx, 253 request: req, 254 response: rsp, 255 codec: codec, 256 // used to close the stream 257 closed: make(chan bool), 258 // signal the end of stream, 259 sendEOS: true, 260 // release func 261 release: func(err error) { c.Close() }, 262 } 263 264 // wait for error response 265 ch := make(chan error, 1) 266 267 go func() { 268 // send the first message 269 ch <- stream.Send(req.Body()) 270 }() 271 272 var grr error 273 274 select { 275 case err := <-ch: 276 grr = err 277 case <-ctx.Done(): 278 grr = errors.Timeout("go.micro.client", fmt.Sprintf("%v", ctx.Err())) 279 } 280 281 if grr != nil { 282 // set the error 283 stream.Lock() 284 stream.err = grr 285 stream.Unlock() 286 287 // close the stream 288 stream.Close() 289 return nil, grr 290 } 291 292 return stream, nil 293 } 294 295 func (r *rpcClient) Init(opts ...Option) error { 296 size := r.opts.PoolSize 297 ttl := r.opts.PoolTTL 298 tr := r.opts.Transport 299 300 for _, o := range opts { 301 o(&r.opts) 302 } 303 304 // update pool configuration if the options changed 305 if size != r.opts.PoolSize || ttl != r.opts.PoolTTL || tr != r.opts.Transport { 306 // close existing pool 307 r.pool.Close() 308 // create new pool 309 r.pool = pool.NewPool( 310 pool.Size(r.opts.PoolSize), 311 pool.TTL(r.opts.PoolTTL), 312 pool.Transport(r.opts.Transport), 313 ) 314 } 315 316 return nil 317 } 318 319 func (r *rpcClient) Options() Options { 320 return r.opts 321 } 322 323 // next returns an iterator for the next nodes to call 324 func (r *rpcClient) next(request Request, opts CallOptions) (selector.Next, error) { 325 // try get the proxy 326 service, address, _ := net.Proxy(request.Service(), opts.Address) 327 328 // return remote address 329 if len(address) > 0 { 330 nodes := make([]*registry.Node, len(address)) 331 332 for i, addr := range address { 333 nodes[i] = ®istry.Node{ 334 Address: addr, 335 // Set the protocol 336 Metadata: map[string]string{ 337 "protocol": "mucp", 338 }, 339 } 340 } 341 342 // crude return method 343 return func() (*registry.Node, error) { 344 return nodes[time.Now().Unix()%int64(len(nodes))], nil 345 }, nil 346 } 347 348 // get next nodes from the selector 349 next, err := r.opts.Selector.Select(service, opts.SelectOptions...) 350 if err != nil { 351 if err == selector.ErrNotFound { 352 return nil, errors.InternalServerError("go.micro.client", "service %s: %s", service, err.Error()) 353 } 354 return nil, errors.InternalServerError("go.micro.client", "error selecting %s node: %s", service, err.Error()) 355 } 356 357 return next, nil 358 } 359 360 func (r *rpcClient) Call(ctx context.Context, request Request, response interface{}, opts ...CallOption) error { 361 // make a copy of call opts 362 callOpts := r.opts.CallOptions 363 for _, opt := range opts { 364 opt(&callOpts) 365 } 366 367 next, err := r.next(request, callOpts) 368 if err != nil { 369 return err 370 } 371 372 // check if we already have a deadline 373 d, ok := ctx.Deadline() 374 if !ok { 375 // no deadline so we create a new one 376 var cancel context.CancelFunc 377 ctx, cancel = context.WithTimeout(ctx, callOpts.RequestTimeout) 378 defer cancel() 379 } else { 380 // got a deadline so no need to setup context 381 // but we need to set the timeout we pass along 382 opt := WithRequestTimeout(d.Sub(time.Now())) 383 opt(&callOpts) 384 } 385 386 // should we noop right here? 387 select { 388 case <-ctx.Done(): 389 return errors.Timeout("go.micro.client", fmt.Sprintf("%v", ctx.Err())) 390 default: 391 } 392 393 // make copy of call method 394 rcall := r.call 395 396 // wrap the call in reverse 397 for i := len(callOpts.CallWrappers); i > 0; i-- { 398 rcall = callOpts.CallWrappers[i-1](rcall) 399 } 400 401 // return errors.New("go.micro.client", "request timeout", 408) 402 call := func(i int) error { 403 // call backoff first. Someone may want an initial start delay 404 t, err := callOpts.Backoff(ctx, request, i) 405 if err != nil { 406 return errors.InternalServerError("go.micro.client", "backoff error: %v", err.Error()) 407 } 408 409 // only sleep if greater than 0 410 if t.Seconds() > 0 { 411 time.Sleep(t) 412 } 413 414 // select next node 415 node, err := next() 416 service := request.Service() 417 if err != nil { 418 if err == selector.ErrNotFound { 419 return errors.InternalServerError("go.micro.client", "service %s: %s", service, err.Error()) 420 } 421 return errors.InternalServerError("go.micro.client", "error getting next %s node: %s", service, err.Error()) 422 } 423 424 // make the call 425 err = rcall(ctx, node, request, response, callOpts) 426 r.opts.Selector.Mark(service, node, err) 427 return err 428 } 429 430 // get the retries 431 retries := callOpts.Retries 432 433 // disable retries when using a proxy 434 if _, _, ok := net.Proxy(request.Service(), callOpts.Address); ok { 435 retries = 0 436 } 437 438 ch := make(chan error, retries+1) 439 var gerr error 440 441 for i := 0; i <= retries; i++ { 442 go func(i int) { 443 ch <- call(i) 444 }(i) 445 446 select { 447 case <-ctx.Done(): 448 return errors.Timeout("go.micro.client", fmt.Sprintf("call timeout: %v", ctx.Err())) 449 case err := <-ch: 450 // if the call succeeded lets bail early 451 if err == nil { 452 return nil 453 } 454 455 retry, rerr := callOpts.Retry(ctx, request, i, err) 456 if rerr != nil { 457 return rerr 458 } 459 460 if !retry { 461 return err 462 } 463 464 gerr = err 465 } 466 } 467 468 return gerr 469 } 470 471 func (r *rpcClient) Stream(ctx context.Context, request Request, opts ...CallOption) (Stream, error) { 472 // make a copy of call opts 473 callOpts := r.opts.CallOptions 474 for _, opt := range opts { 475 opt(&callOpts) 476 } 477 478 next, err := r.next(request, callOpts) 479 if err != nil { 480 return nil, err 481 } 482 483 // should we noop right here? 484 select { 485 case <-ctx.Done(): 486 return nil, errors.Timeout("go.micro.client", fmt.Sprintf("%v", ctx.Err())) 487 default: 488 } 489 490 call := func(i int) (Stream, error) { 491 // call backoff first. Someone may want an initial start delay 492 t, err := callOpts.Backoff(ctx, request, i) 493 if err != nil { 494 return nil, errors.InternalServerError("go.micro.client", "backoff error: %v", err.Error()) 495 } 496 497 // only sleep if greater than 0 498 if t.Seconds() > 0 { 499 time.Sleep(t) 500 } 501 502 node, err := next() 503 service := request.Service() 504 if err != nil { 505 if err == selector.ErrNotFound { 506 return nil, errors.InternalServerError("go.micro.client", "service %s: %s", service, err.Error()) 507 } 508 return nil, errors.InternalServerError("go.micro.client", "error getting next %s node: %s", service, err.Error()) 509 } 510 511 stream, err := r.stream(ctx, node, request, callOpts) 512 r.opts.Selector.Mark(service, node, err) 513 return stream, err 514 } 515 516 type response struct { 517 stream Stream 518 err error 519 } 520 521 // get the retries 522 retries := callOpts.Retries 523 524 // disable retries when using a proxy 525 if _, _, ok := net.Proxy(request.Service(), callOpts.Address); ok { 526 retries = 0 527 } 528 529 ch := make(chan response, retries+1) 530 var grr error 531 532 for i := 0; i <= retries; i++ { 533 go func(i int) { 534 s, err := call(i) 535 ch <- response{s, err} 536 }(i) 537 538 select { 539 case <-ctx.Done(): 540 return nil, errors.Timeout("go.micro.client", fmt.Sprintf("call timeout: %v", ctx.Err())) 541 case rsp := <-ch: 542 // if the call succeeded lets bail early 543 if rsp.err == nil { 544 return rsp.stream, nil 545 } 546 547 retry, rerr := callOpts.Retry(ctx, request, i, rsp.err) 548 if rerr != nil { 549 return nil, rerr 550 } 551 552 if !retry { 553 return nil, rsp.err 554 } 555 556 grr = rsp.err 557 } 558 } 559 560 return nil, grr 561 } 562 563 func (r *rpcClient) Publish(ctx context.Context, msg Message, opts ...PublishOption) error { 564 options := PublishOptions{ 565 Context: context.Background(), 566 } 567 for _, o := range opts { 568 o(&options) 569 } 570 571 md, ok := metadata.FromContext(ctx) 572 if !ok { 573 md = make(map[string]string) 574 } 575 576 id := uuid.New().String() 577 md["Content-Type"] = msg.ContentType() 578 md["Micro-Topic"] = msg.Topic() 579 md["Micro-Id"] = id 580 581 // set the topic 582 topic := msg.Topic() 583 584 // get the exchange 585 if len(options.Exchange) > 0 { 586 topic = options.Exchange 587 } 588 589 // encode message body 590 cf, err := r.newCodec(msg.ContentType()) 591 if err != nil { 592 return errors.InternalServerError("go.micro.client", err.Error()) 593 } 594 595 var body []byte 596 597 // passed in raw data 598 if d, ok := msg.Payload().(*raw.Frame); ok { 599 body = d.Data 600 } else { 601 // new buffer 602 b := buf.New(nil) 603 604 if err := cf(b).Write(&codec.Message{ 605 Target: topic, 606 Type: codec.Event, 607 Header: map[string]string{ 608 "Micro-Id": id, 609 "Micro-Topic": msg.Topic(), 610 }, 611 }, msg.Payload()); err != nil { 612 return errors.InternalServerError("go.micro.client", err.Error()) 613 } 614 615 // set the body 616 body = b.Bytes() 617 } 618 619 if !r.once.Load().(bool) { 620 if err = r.opts.Broker.Connect(); err != nil { 621 return errors.InternalServerError("go.micro.client", err.Error()) 622 } 623 r.once.Store(true) 624 } 625 626 return r.opts.Broker.Publish(topic, &broker.Message{ 627 Header: md, 628 Body: body, 629 }, broker.PublishContext(options.Context)) 630 } 631 632 func (r *rpcClient) NewMessage(topic string, message interface{}, opts ...MessageOption) Message { 633 return newMessage(topic, message, r.opts.ContentType, opts...) 634 } 635 636 func (r *rpcClient) NewRequest(service, method string, request interface{}, reqOpts ...RequestOption) Request { 637 return newRequest(service, method, request, r.opts.ContentType, reqOpts...) 638 } 639 640 func (r *rpcClient) String() string { 641 return "mucp" 642 }