github.com/micro/go-micro/v2@v2.9.1/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/micro/go-micro/v2/client"
    14  	"github.com/micro/go-micro/v2/client/selector"
    15  	"github.com/micro/go-micro/v2/codec"
    16  	"github.com/micro/go-micro/v2/codec/bytes"
    17  	"github.com/micro/go-micro/v2/errors"
    18  	"github.com/micro/go-micro/v2/logger"
    19  	"github.com/micro/go-micro/v2/metadata"
    20  	"github.com/micro/go-micro/v2/proxy"
    21  	"github.com/micro/go-micro/v2/router"
    22  	"github.com/micro/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  }