github.com/tickoalcantara12/micro/v3@v3.0.0-20221007104245-9d75b9bcbab9/service/proxy/grpc/grpc.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/grpc/grpc.go
    16  
    17  // Package grpc is a grpc proxy built for the go-micro/server
    18  package grpc
    19  
    20  import (
    21  	"context"
    22  	"io"
    23  	"strings"
    24  
    25  	"github.com/tickoalcantara12/micro/v3/service/client"
    26  	grpcc "github.com/tickoalcantara12/micro/v3/service/client/grpc"
    27  	"github.com/tickoalcantara12/micro/v3/service/errors"
    28  	"github.com/tickoalcantara12/micro/v3/service/logger"
    29  	"github.com/tickoalcantara12/micro/v3/service/proxy"
    30  	"github.com/tickoalcantara12/micro/v3/service/server"
    31  	"github.com/tickoalcantara12/micro/v3/util/codec"
    32  	"github.com/tickoalcantara12/micro/v3/util/codec/bytes"
    33  	"google.golang.org/grpc"
    34  )
    35  
    36  // Proxy will transparently proxy requests to an endpoint.
    37  // If no endpoint is specified it will call a service using the client.
    38  type Proxy struct {
    39  	// embed options
    40  	options proxy.Options
    41  
    42  	// The client to use for outbound requests in the local network
    43  	Client client.Client
    44  
    45  	// Endpoint to route all calls to
    46  	Endpoint string
    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  		select {
    58  		case <-s.Context().Done():
    59  			return nil
    60  		default:
    61  		}
    62  		body, err := r.Read()
    63  		if err == io.EOF {
    64  			return s.Close()
    65  		} else if err != nil {
    66  			return err
    67  		}
    68  
    69  		// get the header from client
    70  		hdr := r.Header()
    71  		msg := &codec.Message{
    72  			Type:   codec.Request,
    73  			Header: hdr,
    74  			Body:   body,
    75  		}
    76  
    77  		// send the message to the stream
    78  		if err := req.Codec().Write(msg, nil); err != nil {
    79  			return err
    80  		}
    81  	}
    82  }
    83  
    84  // ProcessMessage acts as a message exchange and forwards messages to ongoing topics
    85  // TODO: should we look at p.Endpoint and only send to the local endpoint? probably
    86  func (p *Proxy) ProcessMessage(ctx context.Context, msg server.Message) error {
    87  	// TODO: check that we're not broadcast storming by sending to the same topic
    88  	// that we're actually subscribed to
    89  
    90  	if logger.V(logger.TraceLevel, logger.DefaultLogger) {
    91  		logger.Tracef("Proxy received message for %s", msg.Topic())
    92  	}
    93  
    94  	// directly publish to the local client
    95  	return p.Client.Publish(ctx, msg)
    96  }
    97  
    98  // ServeRequest honours the server.Router interface
    99  func (p *Proxy) ServeRequest(ctx context.Context, req server.Request, rsp server.Response) error {
   100  	// service name to call
   101  	service := req.Service()
   102  	// endpoint to call
   103  	endpoint := req.Endpoint()
   104  
   105  	if len(service) == 0 {
   106  		return errors.BadRequest("go.micro.proxy", "service name is blank")
   107  	}
   108  
   109  	if logger.V(logger.TraceLevel, logger.DefaultLogger) {
   110  		logger.Tracef("Proxy received request for %s %s", service, endpoint)
   111  	}
   112  
   113  	var opts []client.CallOption
   114  
   115  	// call a specific backend
   116  	if len(p.Endpoint) > 0 {
   117  		// address:port
   118  		if parts := strings.Split(p.Endpoint, ":"); len(parts) > 1 {
   119  			opts = append(opts, client.WithAddress(p.Endpoint))
   120  			// use as service name
   121  		} else {
   122  			service = p.Endpoint
   123  		}
   124  	}
   125  
   126  	// serve the normal way
   127  	return p.serveRequest(ctx, p.Client, service, endpoint, req, rsp, opts...)
   128  }
   129  
   130  func (p *Proxy) serveRequest(ctx context.Context, link client.Client, service, endpoint string, req server.Request, rsp server.Response, opts ...client.CallOption) error {
   131  	// read initial request
   132  	body, err := req.Read()
   133  	if err != nil {
   134  		return err
   135  	}
   136  
   137  	// create new request with raw bytes body
   138  	creq := link.NewRequest(service, endpoint, &bytes.Frame{Data: body}, client.WithContentType(req.ContentType()))
   139  
   140  	// not a stream so make a client.Call request
   141  	if !req.Stream() {
   142  		crsp := new(bytes.Frame)
   143  
   144  		// make a call to the backend
   145  		if err := link.Call(ctx, creq, crsp, opts...); err != nil {
   146  			return err
   147  		}
   148  
   149  		// write the response
   150  		if err := rsp.Write(crsp.Data); err != nil {
   151  			return err
   152  		}
   153  
   154  		return nil
   155  	}
   156  
   157  	// new context with cancel
   158  	ctx, cancel := context.WithCancel(ctx)
   159  
   160  	// create new stream
   161  	stream, err := link.Stream(ctx, creq, opts...)
   162  	if err != nil {
   163  		return err
   164  	}
   165  	defer stream.Close()
   166  
   167  	// with a grpc stream we have to refire the initial request
   168  	// client request to start the server side
   169  
   170  	// get the header from client
   171  	msg := &codec.Message{
   172  		Type:   codec.Request,
   173  		Header: req.Header(),
   174  		Body:   body,
   175  	}
   176  
   177  	// write the raw request
   178  	err = stream.Request().Codec().Write(msg, nil)
   179  	if err == io.EOF {
   180  		return nil
   181  	} else if err != nil {
   182  		return err
   183  	}
   184  
   185  	// create client request read loop if streaming
   186  	go func() {
   187  		if err := readLoop(req, stream); err != nil {
   188  			// cancel the context
   189  			cancel()
   190  		}
   191  	}()
   192  
   193  	// get raw response
   194  	resp := stream.Response()
   195  
   196  	// create server response write loop
   197  	for {
   198  		// read backend response body
   199  		body, err := resp.Read()
   200  		if err != nil {
   201  			// when we're done if its a grpc stream we have to set the trailer
   202  			if cc, ok := stream.(grpc.ClientStream); ok {
   203  				if ss, ok := resp.Codec().(grpc.ServerStream); ok {
   204  					ss.SetTrailer(cc.Trailer())
   205  				}
   206  			}
   207  		}
   208  
   209  		if err == io.EOF {
   210  			return nil
   211  		} else if err != nil {
   212  			return err
   213  		}
   214  
   215  		// read backend response header
   216  		hdr := resp.Header()
   217  
   218  		// write raw response header to client
   219  		rsp.WriteHeader(hdr)
   220  
   221  		// write raw response body to client
   222  		err = rsp.Write(body)
   223  		if err == io.EOF {
   224  			return nil
   225  		} else if err != nil {
   226  			return err
   227  		}
   228  	}
   229  }
   230  
   231  func (p *Proxy) String() string {
   232  	return "grpc"
   233  }
   234  
   235  // NewProxy returns a new proxy which will route based on mucp headers
   236  func NewProxy(opts ...proxy.Option) proxy.Proxy {
   237  	var options proxy.Options
   238  
   239  	for _, o := range opts {
   240  		o(&options)
   241  	}
   242  
   243  	// create a new grpc proxy
   244  	p := new(Proxy)
   245  	p.options = options
   246  
   247  	// set the client
   248  	p.Client = options.Client
   249  	// set the endpoint
   250  	p.Endpoint = options.Endpoint
   251  
   252  	// set the default client
   253  	if p.Client == nil {
   254  		p.Client = grpcc.NewClient()
   255  	}
   256  
   257  	return p
   258  }