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  }