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