github.com/tickoalcantara12/micro/v3@v3.0.0-20221007104245-9d75b9bcbab9/service/client/mucp/mucp.go (about) 1 // Copyright 2020 Asim Aslam 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // https://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 // 15 // Original source: github.com/micro/go-micro/v3/client/mucp/mucp.go 16 17 // Package mucp provides a transport agnostic RPC client 18 package mucp 19 20 import ( 21 "context" 22 "fmt" 23 "sync/atomic" 24 "time" 25 26 "github.com/google/uuid" 27 "github.com/tickoalcantara12/micro/v3/service/broker" 28 "github.com/tickoalcantara12/micro/v3/service/client" 29 "github.com/tickoalcantara12/micro/v3/service/context/metadata" 30 "github.com/tickoalcantara12/micro/v3/service/errors" 31 "github.com/tickoalcantara12/micro/v3/service/network/transport" 32 "github.com/tickoalcantara12/micro/v3/util/buf" 33 "github.com/tickoalcantara12/micro/v3/util/codec" 34 raw "github.com/tickoalcantara12/micro/v3/util/codec/bytes" 35 "github.com/tickoalcantara12/micro/v3/util/pool" 36 ) 37 38 type rpcClient struct { 39 once atomic.Value 40 opts client.Options 41 pool pool.Pool 42 seq uint64 43 } 44 45 // NewClient returns a new micro client interface 46 func NewClient(opt ...client.Option) client.Client { 47 opts := client.NewOptions(opt...) 48 49 p := pool.NewPool( 50 pool.Size(opts.PoolSize), 51 pool.TTL(opts.PoolTTL), 52 pool.Transport(opts.Transport), 53 ) 54 55 rc := &rpcClient{ 56 opts: opts, 57 pool: p, 58 seq: 0, 59 } 60 rc.once.Store(false) 61 62 c := client.Client(rc) 63 64 // wrap in reverse 65 for i := len(opts.Wrappers); i > 0; i-- { 66 c = opts.Wrappers[i-1](c) 67 } 68 69 return c 70 } 71 72 func (r *rpcClient) newCodec(contentType string) (codec.NewCodec, error) { 73 if c, ok := r.opts.Codecs[contentType]; ok { 74 return c, nil 75 } 76 if cf, ok := DefaultCodecs[contentType]; ok { 77 return cf, nil 78 } 79 return nil, fmt.Errorf("Unsupported Content-Type: %s", contentType) 80 } 81 82 func (r *rpcClient) call(ctx context.Context, addr string, req client.Request, resp interface{}, opts client.CallOptions) error { 83 msg := &transport.Message{ 84 Header: make(map[string]string), 85 } 86 87 md, ok := metadata.FromContext(ctx) 88 if ok { 89 for k, v := range md { 90 // don't copy Micro-Topic header, that used for pub/sub 91 // this fix case then client uses the same context that received in subscriber 92 if k == "Micro-Topic" { 93 continue 94 } 95 msg.Header[k] = v 96 } 97 } 98 99 // set timeout in nanoseconds 100 msg.Header["Timeout"] = fmt.Sprintf("%d", opts.RequestTimeout) 101 // set the content type for the request 102 msg.Header["Content-Type"] = req.ContentType() 103 // set the accept header 104 msg.Header["Accept"] = req.ContentType() 105 106 cf, err := r.newCodec(req.ContentType()) 107 if err != nil { 108 return errors.InternalServerError("go.micro.client", err.Error()) 109 } 110 111 dOpts := []transport.DialOption{ 112 transport.WithStream(), 113 } 114 115 if opts.DialTimeout >= 0 { 116 dOpts = append(dOpts, transport.WithTimeout(opts.DialTimeout)) 117 } 118 119 c, err := r.pool.Get(addr, dOpts...) 120 if err != nil { 121 return errors.InternalServerError("go.micro.client", "connection error: %v", err) 122 } 123 124 seq := atomic.AddUint64(&r.seq, 1) - 1 125 codec := newRpcCodec(msg, c, cf, "") 126 127 rsp := &rpcResponse{ 128 socket: c, 129 codec: codec, 130 } 131 132 stream := &rpcStream{ 133 id: fmt.Sprintf("%v", seq), 134 context: ctx, 135 request: req, 136 response: rsp, 137 codec: codec, 138 closed: make(chan bool), 139 release: func(err error) { r.pool.Release(c, err) }, 140 sendEOS: false, 141 } 142 // close the stream on exiting this function 143 defer stream.Close() 144 145 // wait for error response 146 ch := make(chan error, 1) 147 148 go func() { 149 defer func() { 150 if r := recover(); r != nil { 151 ch <- errors.InternalServerError("go.micro.client", "panic recovered: %v", r) 152 } 153 }() 154 155 // send request 156 if err := stream.Send(req.Body()); err != nil { 157 ch <- err 158 return 159 } 160 161 // recv request 162 if err := stream.Recv(resp); err != nil { 163 ch <- err 164 return 165 } 166 167 // success 168 ch <- nil 169 }() 170 171 var grr error 172 173 select { 174 case err := <-ch: 175 return err 176 case <-ctx.Done(): 177 grr = errors.Timeout("go.micro.client", fmt.Sprintf("%v", ctx.Err())) 178 } 179 180 // set the stream error 181 if grr != nil { 182 stream.Lock() 183 stream.err = grr 184 stream.Unlock() 185 186 return grr 187 } 188 189 return nil 190 } 191 192 func (r *rpcClient) stream(ctx context.Context, addr string, req client.Request, opts client.CallOptions) (client.Stream, error) { 193 msg := &transport.Message{ 194 Header: make(map[string]string), 195 } 196 197 md, ok := metadata.FromContext(ctx) 198 if ok { 199 for k, v := range md { 200 msg.Header[k] = v 201 } 202 } 203 204 // set timeout in nanoseconds 205 if opts.StreamTimeout > time.Duration(0) { 206 msg.Header["Timeout"] = fmt.Sprintf("%d", opts.StreamTimeout) 207 } 208 // set the content type for the request 209 msg.Header["Content-Type"] = req.ContentType() 210 // set the accept header 211 msg.Header["Accept"] = req.ContentType() 212 213 cf, err := r.newCodec(req.ContentType()) 214 if err != nil { 215 return nil, errors.InternalServerError("go.micro.client", err.Error()) 216 } 217 218 dOpts := []transport.DialOption{ 219 transport.WithStream(), 220 } 221 222 if opts.DialTimeout >= 0 { 223 dOpts = append(dOpts, transport.WithTimeout(opts.DialTimeout)) 224 } 225 226 c, err := r.opts.Transport.Dial(addr, dOpts...) 227 if err != nil { 228 return nil, errors.InternalServerError("go.micro.client", "connection error: %v", err) 229 } 230 231 // increment the sequence number 232 seq := atomic.AddUint64(&r.seq, 1) - 1 233 id := fmt.Sprintf("%v", seq) 234 235 // create codec with stream id 236 codec := newRpcCodec(msg, c, cf, id) 237 238 rsp := &rpcResponse{ 239 socket: c, 240 codec: codec, 241 } 242 243 // set request codec 244 if r, ok := req.(*rpcRequest); ok { 245 r.codec = codec 246 } 247 248 stream := &rpcStream{ 249 id: id, 250 context: ctx, 251 request: req, 252 response: rsp, 253 codec: codec, 254 // used to close the stream 255 closed: make(chan bool), 256 // signal the end of stream, 257 sendEOS: true, 258 // release func 259 release: func(err error) { c.Close() }, 260 } 261 262 // wait for error response 263 ch := make(chan error, 1) 264 265 go func() { 266 // send the first message 267 ch <- stream.Send(req.Body()) 268 }() 269 270 var grr error 271 272 select { 273 case err := <-ch: 274 grr = err 275 case <-ctx.Done(): 276 grr = errors.Timeout("go.micro.client", fmt.Sprintf("%v", ctx.Err())) 277 } 278 279 if grr != nil { 280 // set the error 281 stream.Lock() 282 stream.err = grr 283 stream.Unlock() 284 285 // close the stream 286 stream.Close() 287 return nil, grr 288 } 289 290 return stream, nil 291 } 292 293 func (r *rpcClient) Init(opts ...client.Option) error { 294 size := r.opts.PoolSize 295 ttl := r.opts.PoolTTL 296 tr := r.opts.Transport 297 298 for _, o := range opts { 299 o(&r.opts) 300 } 301 302 // update pool configuration if the options changed 303 if size != r.opts.PoolSize || ttl != r.opts.PoolTTL || tr != r.opts.Transport { 304 // close existing pool 305 r.pool.Close() 306 // create new pool 307 r.pool = pool.NewPool( 308 pool.Size(r.opts.PoolSize), 309 pool.TTL(r.opts.PoolTTL), 310 pool.Transport(r.opts.Transport), 311 ) 312 } 313 314 return nil 315 } 316 317 func (r *rpcClient) Options() client.Options { 318 return r.opts 319 } 320 321 func (r *rpcClient) Call(ctx context.Context, request client.Request, response interface{}, opts ...client.CallOption) error { 322 // make a copy of call opts 323 callOpts := r.opts.CallOptions 324 for _, opt := range opts { 325 opt(&callOpts) 326 } 327 328 // check if we already have a deadline 329 if d, ok := ctx.Deadline(); !ok { 330 // no deadline so we create a new one 331 var cancel context.CancelFunc 332 ctx, cancel = context.WithTimeout(ctx, callOpts.RequestTimeout) 333 defer cancel() 334 } else { 335 // got a deadline so no need to setup context 336 // but we need to set the timeout we pass along 337 remaining := d.Sub(time.Now()) 338 client.WithRequestTimeout(remaining)(&callOpts) 339 } 340 341 // should we noop right here? 342 select { 343 case <-ctx.Done(): 344 return errors.Timeout("go.micro.client", fmt.Sprintf("%v", ctx.Err())) 345 default: 346 } 347 348 // make copy of call method 349 rcall := r.call 350 351 // wrap the call in reverse 352 for i := len(callOpts.CallWrappers); i > 0; i-- { 353 rcall = callOpts.CallWrappers[i-1](rcall) 354 } 355 356 // use the router passed as a call option, or fallback to the rpc clients router 357 if callOpts.Router == nil { 358 callOpts.Router = r.opts.Router 359 } 360 361 if callOpts.Selector == nil { 362 callOpts.Selector = r.opts.Selector 363 } 364 365 // inject proxy address 366 // TODO: don't even bother using Lookup/Select in this case 367 if len(r.opts.Proxy) > 0 { 368 callOpts.Address = []string{r.opts.Proxy} 369 } 370 371 // lookup the route to send the reques to 372 // TODO apply any filtering here 373 routes, err := r.opts.Lookup(ctx, request, callOpts) 374 if err != nil { 375 return errors.InternalServerError("go.micro.client", err.Error()) 376 } 377 378 // balance the list of nodes 379 next, err := callOpts.Selector.Select(routes) 380 if err != nil { 381 return err 382 } 383 384 // return errors.New("go.micro.client", "request timeout", 408) 385 call := func(i int) error { 386 // call backoff first. Someone may want an initial start delay 387 t, err := callOpts.Backoff(ctx, request, i) 388 if err != nil { 389 return errors.InternalServerError("go.micro.client", "backoff error: %v", err.Error()) 390 } 391 392 // only sleep if greater than 0 393 if t.Seconds() > 0 { 394 time.Sleep(t) 395 } 396 397 // get the next node 398 node := next() 399 400 // make the call 401 err = rcall(ctx, node, request, response, callOpts) 402 403 // record the result of the call to inform future routing decisions 404 r.opts.Selector.Record(node, err) 405 406 return err 407 } 408 409 // get the retries 410 retries := callOpts.Retries 411 412 // disable retries when using a proxy 413 if len(r.opts.Proxy) > 0 { 414 retries = 0 415 } 416 417 ch := make(chan error, retries+1) 418 var gerr error 419 420 for i := 0; i <= retries; i++ { 421 go func(i int) { 422 ch <- call(i) 423 }(i) 424 425 select { 426 case <-ctx.Done(): 427 return errors.Timeout("go.micro.client", fmt.Sprintf("call timeout: %v", ctx.Err())) 428 case err := <-ch: 429 // if the call succeeded lets bail early 430 if err == nil { 431 return nil 432 } 433 434 retry, rerr := callOpts.Retry(ctx, request, i, err) 435 if rerr != nil { 436 return rerr 437 } 438 439 if !retry { 440 return err 441 } 442 443 gerr = err 444 } 445 } 446 447 return gerr 448 } 449 450 func (r *rpcClient) Stream(ctx context.Context, request client.Request, opts ...client.CallOption) (client.Stream, error) { 451 // make a copy of call opts 452 callOpts := r.opts.CallOptions 453 for _, opt := range opts { 454 opt(&callOpts) 455 } 456 457 // should we noop right here? 458 select { 459 case <-ctx.Done(): 460 return nil, errors.Timeout("go.micro.client", fmt.Sprintf("%v", ctx.Err())) 461 default: 462 } 463 464 // use the router passed as a call option, or fallback to the rpc clients router 465 if callOpts.Router == nil { 466 callOpts.Router = r.opts.Router 467 } 468 469 if callOpts.Selector == nil { 470 callOpts.Selector = r.opts.Selector 471 } 472 473 // inject proxy address 474 // TODO: don't even bother using Lookup/Select in this case 475 if len(r.opts.Proxy) > 0 { 476 callOpts.Address = []string{r.opts.Proxy} 477 } 478 479 // lookup the route to send the reques to 480 // TODO apply any filtering here 481 routes, err := r.opts.Lookup(ctx, request, callOpts) 482 if err != nil { 483 return nil, errors.InternalServerError("go.micro.client", err.Error()) 484 } 485 486 // balance the list of nodes 487 next, err := callOpts.Selector.Select(routes) 488 if err != nil { 489 return nil, err 490 } 491 492 call := func(i int) (client.Stream, error) { 493 // call backoff first. Someone may want an initial start delay 494 t, err := callOpts.Backoff(ctx, request, i) 495 if err != nil { 496 return nil, errors.InternalServerError("go.micro.client", "backoff error: %v", err.Error()) 497 } 498 499 // only sleep if greater than 0 500 if t.Seconds() > 0 { 501 time.Sleep(t) 502 } 503 504 // get the next node 505 node := next() 506 507 // perform the call 508 stream, err := r.stream(ctx, node, request, callOpts) 509 510 // record the result of the call to inform future routing decisions 511 r.opts.Selector.Record(node, err) 512 513 return stream, err 514 } 515 516 type response struct { 517 stream client.Stream 518 err error 519 } 520 521 // get the retries 522 retries := callOpts.Retries 523 524 // disable retries when using a proxy 525 if len(r.opts.Proxy) > 0 { 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 client.Message, opts ...client.PublishOption) error { 564 options := client.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 ...client.MessageOption) client.Message { 633 return newMessage(topic, message, r.opts.ContentType, opts...) 634 } 635 636 func (r *rpcClient) NewRequest(service, method string, request interface{}, reqOpts ...client.RequestOption) client.Request { 637 return newRequest(service, method, request, r.opts.ContentType, reqOpts...) 638 } 639 640 func (r *rpcClient) String() string { 641 return "mucp" 642 }