github.com/tickoalcantara12/micro/v3@v3.0.0-20221007104245-9d75b9bcbab9/service/proxy/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/proxy/mucp/mucp.go 16 17 // Package mucp transparently forwards the incoming request using a go-micro client. 18 package mucp 19 20 import ( 21 "context" 22 "fmt" 23 "io" 24 "sort" 25 "strings" 26 "sync" 27 "time" 28 29 "github.com/tickoalcantara12/micro/v3/service/client" 30 grpcc "github.com/tickoalcantara12/micro/v3/service/client/grpc" 31 "github.com/tickoalcantara12/micro/v3/service/context/metadata" 32 "github.com/tickoalcantara12/micro/v3/service/errors" 33 "github.com/tickoalcantara12/micro/v3/service/logger" 34 "github.com/tickoalcantara12/micro/v3/service/proxy" 35 "github.com/tickoalcantara12/micro/v3/service/router" 36 "github.com/tickoalcantara12/micro/v3/service/router/registry" 37 "github.com/tickoalcantara12/micro/v3/service/server" 38 "github.com/tickoalcantara12/micro/v3/util/codec" 39 "github.com/tickoalcantara12/micro/v3/util/codec/bytes" 40 "github.com/tickoalcantara12/micro/v3/util/selector" 41 "github.com/tickoalcantara12/micro/v3/util/selector/roundrobin" 42 "google.golang.org/grpc" 43 ) 44 45 // Proxy will transparently proxy requests to an endpoint. 46 // If no endpoint is specified it will call a service using the client. 47 type Proxy struct { 48 // embed options 49 options proxy.Options 50 51 // Endpoint specifies the fixed service endpoint to call. 52 Endpoint string 53 54 // The client to use for outbound requests in the local network 55 Client client.Client 56 57 // Links are used for outbound requests not in the local network 58 Links map[string]client.Client 59 60 // The router for routes 61 Router router.Router 62 63 // A fib of routes service:address 64 sync.RWMutex 65 Routes map[string]map[uint64]router.Route 66 67 // selector used for load balancing 68 Selector selector.Selector 69 } 70 71 // read client request and write to server 72 func readLoop(r server.Request, s client.Stream) error { 73 // request to backend server 74 req := s.Request() 75 76 for { 77 // get data from client 78 // no need to decode it 79 body, err := r.Read() 80 if err == io.EOF { 81 return nil 82 } 83 84 if err != nil { 85 return err 86 } 87 88 // get the header from client 89 hdr := r.Header() 90 msg := &codec.Message{ 91 Type: codec.Request, 92 Header: hdr, 93 Body: body, 94 } 95 96 // write the raw request 97 err = req.Codec().Write(msg, nil) 98 if err == io.EOF { 99 return nil 100 } else if err != nil { 101 return err 102 } 103 } 104 } 105 106 // toNodes returns a list of node addresses from given routes 107 func toNodes(routes []router.Route) []string { 108 nodes := make([]string, 0, len(routes)) 109 110 for _, node := range routes { 111 address := node.Address 112 if len(node.Gateway) > 0 { 113 address = node.Gateway 114 } 115 nodes = append(nodes, address) 116 } 117 118 return nodes 119 } 120 121 func toSlice(r map[uint64]router.Route) []router.Route { 122 routes := make([]router.Route, 0, len(r)) 123 124 for _, v := range r { 125 routes = append(routes, v) 126 } 127 128 // sort the routes in order of metric 129 sort.Slice(routes, func(i, j int) bool { return routes[i].Metric < routes[j].Metric }) 130 131 return routes 132 } 133 134 func (p *Proxy) filterRoutes(ctx context.Context, routes []router.Route) []router.Route { 135 md, ok := metadata.FromContext(ctx) 136 if !ok { 137 return routes 138 } 139 140 //nolint:prealloc 141 var filteredRoutes []router.Route 142 143 // filter the routes based on our headers 144 for _, route := range routes { 145 // process only routes for this id 146 if id, ok := md.Get("Micro-Router"); ok && len(id) > 0 { 147 if route.Router != id { 148 // skip routes that don't match 149 continue 150 } 151 } 152 153 // only process routes with this network 154 if net, ok := md.Get("Micro-Namespace"); ok && len(net) > 0 { 155 if route.Network != router.DefaultNetwork && route.Network != net { 156 // skip routes that don't match 157 continue 158 } 159 } 160 161 // process only this gateway 162 if gw, ok := md.Get("Micro-Gateway"); ok && len(gw) > 0 { 163 // if the gateway matches our address 164 // special case, take the routes with no gateway 165 // TODO: should we strip the gateway from the context? 166 if gw == p.Router.Options().Address { 167 if len(route.Gateway) > 0 && route.Gateway != gw { 168 continue 169 } 170 // otherwise its a local route and we're keeping it 171 } else { 172 // gateway does not match our own 173 if route.Gateway != gw { 174 continue 175 } 176 } 177 } 178 179 // TODO: address based filtering 180 // address := md["Micro-Address"] 181 182 // TODO: label based filtering 183 // requires new field in routing table : route.Labels 184 185 // passed the filter checks 186 filteredRoutes = append(filteredRoutes, route) 187 } 188 189 if logger.V(logger.TraceLevel, logger.DefaultLogger) { 190 logger.Tracef("Proxy filtered routes %+v", filteredRoutes) 191 } 192 193 return filteredRoutes 194 } 195 196 func (p *Proxy) getLink(r router.Route) (client.Client, error) { 197 if r.Link == "local" || len(p.Links) == 0 { 198 return p.Client, nil 199 } 200 l, ok := p.Links[r.Link] 201 if !ok { 202 return nil, errors.InternalServerError("go.micro.proxy", "link not found") 203 } 204 return l, nil 205 } 206 207 func (p *Proxy) getRoute(ctx context.Context, service string) ([]router.Route, error) { 208 // lookup the route cache first 209 p.RLock() 210 cached, ok := p.Routes[service] 211 p.RUnlock() 212 if ok { 213 return p.filterRoutes(ctx, toSlice(cached)), nil 214 } 215 216 // cache routes for the service 217 routes, err := p.cacheRoutes(service) 218 if err != nil { 219 return nil, err 220 } 221 222 return p.filterRoutes(ctx, routes), nil 223 } 224 225 func (p *Proxy) cacheRoutes(service string) ([]router.Route, error) { 226 // lookup the routes in the router 227 results, err := p.Router.Lookup(service, router.LookupNetwork("*")) 228 if err != nil { 229 // assumption that we're ok with stale routes 230 logger.Debugf("Failed to lookup route for %s: %v", service, err) 231 // otherwise return the error 232 return nil, err 233 } 234 235 // update the proxy cache 236 p.Lock() 237 238 // delete the existing reference to the service 239 delete(p.Routes, service) 240 241 for _, route := range results { 242 // create if does not exist 243 if _, ok := p.Routes[service]; !ok { 244 p.Routes[service] = make(map[uint64]router.Route) 245 } 246 // cache the route based on its unique hash 247 p.Routes[service][route.Hash()] = route 248 } 249 250 // make a copy of the service routes 251 routes := p.Routes[service] 252 253 p.Unlock() 254 255 // return routes to the caller 256 return toSlice(routes), nil 257 } 258 259 // refreshMetrics will refresh any metrics for our local cached routes. 260 // we may not receive new watch events for these as they change. 261 func (p *Proxy) refreshMetrics() { 262 // get a list of services to update 263 p.RLock() 264 265 services := make([]string, 0, len(p.Routes)) 266 267 for service := range p.Routes { 268 services = append(services, service) 269 } 270 271 p.RUnlock() 272 273 // get and cache the routes for the service 274 for _, service := range services { 275 p.cacheRoutes(service) 276 } 277 } 278 279 // manageRoutes applies action on a given route to Proxy route cache 280 func (p *Proxy) manageRoutes(route router.Route, action string) error { 281 // we only cache what we are actually concerned with 282 p.Lock() 283 defer p.Unlock() 284 285 if logger.V(logger.TraceLevel, logger.DefaultLogger) { 286 logger.Tracef("Proxy taking route action %v %+v\n", action, route) 287 } 288 289 switch action { 290 case "create", "update": 291 if _, ok := p.Routes[route.Service]; !ok { 292 return fmt.Errorf("not called %s", route.Service) 293 } 294 p.Routes[route.Service][route.Hash()] = route 295 case "delete": 296 // delete that specific route 297 delete(p.Routes[route.Service], route.Hash()) 298 // clean up the cache entirely 299 if len(p.Routes[route.Service]) == 0 { 300 delete(p.Routes, route.Service) 301 } 302 default: 303 return fmt.Errorf("unknown action: %s", action) 304 } 305 306 return nil 307 } 308 309 // watchRoutes watches service routes and updates proxy cache 310 func (p *Proxy) watchRoutes() { 311 // route watcher 312 w, err := p.Router.Watch() 313 if err != nil { 314 logger.Debugf("Error watching router: %v", err) 315 return 316 } else if w == nil { 317 logger.Debugf("Error watching routes, no watcher returned") 318 return 319 } 320 defer w.Stop() 321 322 for { 323 event, err := w.Next() 324 if err != nil { 325 logger.Debugf("Error watching router: %v", err) 326 return 327 } 328 329 if err := p.manageRoutes(event.Route, event.Type.String()); err != nil { 330 // TODO: should we bail here? 331 continue 332 } 333 } 334 } 335 336 // ProcessMessage acts as a message exchange and forwards messages to ongoing topics 337 // TODO: should we look at p.Endpoint and only send to the local endpoint? probably 338 func (p *Proxy) ProcessMessage(ctx context.Context, msg server.Message) error { 339 // TODO: check that we're not broadcast storming by sending to the same topic 340 // that we're actually subscribed to 341 342 if logger.V(logger.TraceLevel, logger.DefaultLogger) { 343 logger.Tracef("Proxy received message for %s", msg.Topic()) 344 } 345 346 var errors []string 347 348 // directly publish to the local client 349 if err := p.Client.Publish(ctx, msg); err != nil { 350 errors = append(errors, err.Error()) 351 } 352 353 // publish to all links 354 for _, client := range p.Links { 355 if err := client.Publish(ctx, msg); err != nil { 356 errors = append(errors, err.Error()) 357 } 358 } 359 360 if len(errors) == 0 { 361 return nil 362 } 363 364 // there is no error...muahaha 365 return fmt.Errorf("Message processing error: %s", strings.Join(errors, "\n")) 366 } 367 368 // ServeRequest honours the server.Router interface 369 func (p *Proxy) ServeRequest(ctx context.Context, req server.Request, rsp server.Response) error { 370 // determine if its local routing 371 var local bool 372 // address to call 373 var addresses []string 374 // routes 375 var routes []router.Route 376 // service name to call 377 service := req.Service() 378 // endpoint to call 379 endpoint := req.Endpoint() 380 381 if len(service) == 0 { 382 return errors.BadRequest("go.micro.proxy", "service name is blank") 383 } 384 385 if logger.V(logger.TraceLevel, logger.DefaultLogger) { 386 logger.Tracef("Proxy received request for %s %s", service, endpoint) 387 } 388 389 // are we network routing or local routing 390 if len(p.Links) == 0 { 391 local = true 392 } 393 394 // if there is a proxy being used, e.g. micro network, we don't need to 395 // look up the routes since they'll be ignored by the client 396 if len(p.Client.Options().Proxy) > 0 { 397 return p.serveRequest(ctx, p.Client, service, endpoint, req, rsp) 398 } 399 400 // call a specific backend endpoint either by name or address 401 if len(p.Endpoint) > 0 { 402 // address:port 403 if parts := strings.Split(p.Endpoint, ":"); len(parts) > 1 { 404 addresses = []string{p.Endpoint} 405 } else { 406 // get route for endpoint from router 407 addr, err := p.getRoute(ctx, p.Endpoint) 408 if err != nil { 409 return err 410 } 411 // set the address 412 routes = addr 413 // set the name 414 service = p.Endpoint 415 } 416 } else { 417 // no endpoint was specified just lookup the route 418 // get route for endpoint from router 419 addr, err := p.getRoute(ctx, service) 420 if err != nil { 421 return err 422 } 423 routes = addr 424 } 425 426 //nolint:prealloc 427 opts := []client.CallOption{ 428 // set strategy to round robin 429 client.WithSelector(p.Selector), 430 } 431 432 // if the address is already set just serve it 433 // TODO: figure it out if we should know to pick a link 434 if len(addresses) > 0 { 435 opts = append(opts, 436 client.WithAddress(addresses...), 437 ) 438 439 // serve the normal way 440 return p.serveRequest(ctx, p.Client, service, endpoint, req, rsp, opts...) 441 } 442 443 // there's no links e.g we're local routing then just serve it with addresses 444 if local { 445 var opts []client.CallOption 446 447 // set address if available via routes or specific endpoint 448 if len(routes) > 0 { 449 addresses = toNodes(routes) 450 opts = append(opts, client.WithAddress(addresses...)) 451 } 452 453 if logger.V(logger.TraceLevel, logger.DefaultLogger) { 454 logger.Tracef("Proxy calling %+v\n", addresses) 455 } 456 // serve the normal way 457 return p.serveRequest(ctx, p.Client, service, endpoint, req, rsp, opts...) 458 } 459 460 // we're assuming we need routes to operate on 461 if len(routes) == 0 { 462 return errors.InternalServerError("go.micro.proxy", "route not found") 463 } 464 465 var gerr error 466 467 // we're routing globally with multiple links 468 // so we need to pick a link per route 469 for _, route := range routes { 470 // pick the link or error out 471 link, err := p.getLink(route) 472 if err != nil { 473 // ok let's try again 474 gerr = err 475 continue 476 } 477 478 if logger.V(logger.TraceLevel, logger.DefaultLogger) { 479 logger.Tracef("Proxy using route %+v\n", route) 480 } 481 482 // set the address to call 483 addresses := toNodes([]router.Route{route}) 484 // set the address in the options 485 // disable retries since its one route processing 486 opts = append(opts, 487 client.WithAddress(addresses...), 488 client.WithRetries(0), 489 ) 490 491 // do the request with the link 492 gerr = p.serveRequest(ctx, link, service, endpoint, req, rsp, opts...) 493 // return on no error since we succeeded 494 if gerr == nil { 495 return nil 496 } 497 498 // return where the context deadline was exceeded 499 if gerr == context.Canceled || gerr == context.DeadlineExceeded { 500 return err 501 } 502 503 // otherwise attempt to do it all over again 504 } 505 506 // if we got here something went really badly wrong 507 return gerr 508 } 509 510 func (p *Proxy) serveRequest(ctx context.Context, link client.Client, service, endpoint string, req server.Request, rsp server.Response, opts ...client.CallOption) error { 511 // read initial request 512 body, err := req.Read() 513 if err != nil { 514 return err 515 } 516 517 // create new request with raw bytes body 518 creq := link.NewRequest(service, endpoint, &bytes.Frame{Data: body}, client.WithContentType(req.ContentType())) 519 520 // not a stream so make a client.Call request 521 if !req.Stream() { 522 crsp := new(bytes.Frame) 523 524 // make a call to the backend 525 if err := link.Call(ctx, creq, crsp, opts...); err != nil { 526 return err 527 } 528 529 // write the response 530 if err := rsp.Write(crsp.Data); err != nil { 531 return err 532 } 533 534 return nil 535 } 536 537 // new context with cancel 538 ctx, cancel := context.WithCancel(ctx) 539 540 // create new stream 541 stream, err := link.Stream(ctx, creq, opts...) 542 if err != nil { 543 return err 544 } 545 defer stream.Close() 546 547 // if we receive a grpc stream we have to refire the initial request 548 c, ok := req.Codec().(codec.Codec) 549 if ok && c.String() == "grpc" && link.String() == "grpc" { 550 // get the header from client 551 hdr := req.Header() 552 msg := &codec.Message{ 553 Type: codec.Request, 554 Header: hdr, 555 Body: body, 556 } 557 558 // write the raw request 559 err = stream.Request().Codec().Write(msg, nil) 560 if err == io.EOF { 561 return nil 562 } else if err != nil { 563 return err 564 } 565 } 566 567 // create client request read loop if streaming 568 go func() { 569 err := readLoop(req, stream) 570 if err != nil && err != io.EOF { 571 // cancel the context 572 cancel() 573 } 574 }() 575 576 // get raw response 577 resp := stream.Response() 578 579 // create server response write loop 580 for { 581 // read backend response body 582 body, err := resp.Read() 583 if err != nil { 584 // when we're done if its a grpc stream we have to set the trailer 585 if cc, ok := stream.(grpc.ClientStream); ok { 586 if ss, ok := resp.Codec().(grpc.ServerStream); ok { 587 ss.SetTrailer(cc.Trailer()) 588 } 589 } 590 } 591 592 if err == io.EOF { 593 return nil 594 } else if err != nil { 595 return err 596 } 597 598 // read backend response header 599 hdr := resp.Header() 600 601 // write raw response header to client 602 rsp.WriteHeader(hdr) 603 604 // write raw response body to client 605 err = rsp.Write(body) 606 if err == io.EOF { 607 return nil 608 } else if err != nil { 609 return err 610 } 611 } 612 } 613 614 func (p *Proxy) String() string { 615 return "mucp" 616 } 617 618 // NewSingleHostProxy returns a proxy which sends requests to a single backend 619 func NewSingleHostProxy(endpoint string) *Proxy { 620 return &Proxy{ 621 Endpoint: endpoint, 622 } 623 } 624 625 // NewProxy returns a new proxy which will route based on mucp headers 626 func NewProxy(opts ...proxy.Option) proxy.Proxy { 627 var options proxy.Options 628 for _, o := range opts { 629 o(&options) 630 } 631 632 p := new(Proxy) 633 p.Links = map[string]client.Client{} 634 p.Routes = make(map[string]map[uint64]router.Route) 635 p.options = options 636 637 // get endpoint 638 p.Endpoint = options.Endpoint 639 // set the client 640 p.Client = options.Client 641 // get router 642 p.Router = options.Router 643 644 // set the default client 645 if p.Client == nil { 646 p.Client = grpcc.NewClient() 647 } 648 649 // create default router and start it 650 if p.Router == nil { 651 p.Router = registry.NewRouter() 652 } 653 654 if p.Selector == nil { 655 p.Selector = roundrobin.NewSelector() 656 } 657 658 // set the links 659 if options.Links != nil { 660 // get client 661 p.Links = options.Links 662 } 663 664 // TODO: remove this cruft 665 // skip watching routes if proxy is set 666 if len(p.Client.Options().Proxy) > 0 { 667 return p 668 } 669 670 go func() { 671 // continuously attempt to watch routes 672 for { 673 // watch the routes 674 p.watchRoutes() 675 // in case of failure just wait a second 676 time.Sleep(time.Second) 677 } 678 }() 679 680 go func() { 681 // TODO: speed up refreshing of metrics 682 // without this ticking effort e.g stream 683 t := time.NewTicker(time.Second * 10) 684 defer t.Stop() 685 686 // we must refresh route metrics since they do not trigger new events 687 for range t.C { 688 // refresh route metrics 689 p.refreshMetrics() 690 } 691 }() 692 693 return p 694 }