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