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