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  }