github.com/micro/go-micro/v2@v2.9.1/proxy/grpc/grpc.go (about)

     1  // Package grpc transparently forwards the grpc protocol using a go-micro client.
     2  package grpc
     3  
     4  import (
     5  	"context"
     6  	"io"
     7  	"strings"
     8  
     9  	"github.com/micro/go-micro/v2/client"
    10  	"github.com/micro/go-micro/v2/client/grpc"
    11  	"github.com/micro/go-micro/v2/codec"
    12  	"github.com/micro/go-micro/v2/proxy"
    13  	"github.com/micro/go-micro/v2/server"
    14  )
    15  
    16  // Proxy will transparently proxy requests to the backend.
    17  // If no backend is specified it will call a service using the client.
    18  // If the service matches the Name it will use the server.DefaultRouter.
    19  type Proxy struct {
    20  	// The proxy options
    21  	options proxy.Options
    22  
    23  	// Endpoint specified the fixed endpoint to call.
    24  	Endpoint string
    25  
    26  	// The client to use for outbound requests
    27  	Client client.Client
    28  }
    29  
    30  // read client request and write to server
    31  func readLoop(r server.Request, s client.Stream) error {
    32  	// request to backend server
    33  	req := s.Request()
    34  
    35  	for {
    36  		// get data from client
    37  		//  no need to decode it
    38  		body, err := r.Read()
    39  		if err == io.EOF {
    40  			return nil
    41  		}
    42  		if err != nil {
    43  			return err
    44  		}
    45  
    46  		// get the header from client
    47  		hdr := r.Header()
    48  		msg := &codec.Message{
    49  			Type:   codec.Request,
    50  			Header: hdr,
    51  			Body:   body,
    52  		}
    53  		// write the raw request
    54  		err = req.Codec().Write(msg, nil)
    55  		if err == io.EOF {
    56  			return nil
    57  		} else if err != nil {
    58  			return err
    59  		}
    60  	}
    61  }
    62  
    63  // ProcessMessage acts as a message exchange and forwards messages to ongoing topics
    64  // TODO: should we look at p.Endpoint and only send to the local endpoint? probably
    65  func (p *Proxy) ProcessMessage(ctx context.Context, msg server.Message) error {
    66  	// TODO: check that we're not broadcast storming by sending to the same topic
    67  	// that we're actually subscribed to
    68  
    69  	// directly publish to the local client
    70  	return p.Client.Publish(ctx, msg)
    71  }
    72  
    73  // ServeRequest honours the server.Proxy interface
    74  func (p *Proxy) ServeRequest(ctx context.Context, req server.Request, rsp server.Response) error {
    75  	// set default client
    76  	if p.Client == nil {
    77  		p.Client = grpc.NewClient()
    78  	}
    79  
    80  	opts := []client.CallOption{}
    81  
    82  	// service name
    83  	service := req.Service()
    84  	endpoint := req.Endpoint()
    85  
    86  	// call a specific backend
    87  	if len(p.Endpoint) > 0 {
    88  		// address:port
    89  		if parts := strings.Split(p.Endpoint, ":"); len(parts) > 1 {
    90  			opts = append(opts, client.WithAddress(p.Endpoint))
    91  			// use as service name
    92  		} else {
    93  			service = p.Endpoint
    94  		}
    95  	}
    96  
    97  	// create new request with raw bytes body
    98  	creq := p.Client.NewRequest(service, endpoint, nil, client.WithContentType(req.ContentType()))
    99  
   100  	// create new stream
   101  	stream, err := p.Client.Stream(ctx, creq, opts...)
   102  	if err != nil {
   103  		return err
   104  	}
   105  	defer stream.Close()
   106  
   107  	// create client request read loop
   108  	go readLoop(req, stream)
   109  
   110  	// get raw response
   111  	resp := stream.Response()
   112  
   113  	// create server response write loop
   114  	for {
   115  		// read backend response body
   116  		body, err := resp.Read()
   117  		if err == io.EOF {
   118  			return nil
   119  		} else if err != nil {
   120  			return err
   121  		}
   122  
   123  		// read backend response header
   124  		hdr := resp.Header()
   125  
   126  		// write raw response header to client
   127  		rsp.WriteHeader(hdr)
   128  
   129  		// write raw response body to client
   130  		err = rsp.Write(body)
   131  		if err == io.EOF {
   132  			return nil
   133  		} else if err != nil {
   134  			return err
   135  		}
   136  	}
   137  }
   138  
   139  func (p *Proxy) String() string {
   140  	return "grpc"
   141  }
   142  
   143  // NewProxy returns a new grpc proxy server
   144  func NewProxy(opts ...proxy.Option) proxy.Proxy {
   145  	var options proxy.Options
   146  	for _, o := range opts {
   147  		o(&options)
   148  	}
   149  
   150  	p := new(Proxy)
   151  	p.Endpoint = options.Endpoint
   152  	p.Client = options.Client
   153  
   154  	return p
   155  }
   156  
   157  // NewSingleHostProxy returns a router which sends requests to a single backend
   158  func NewSingleHostProxy(url string) *Proxy {
   159  	return &Proxy{
   160  		Endpoint: url,
   161  	}
   162  }