I want to proxy all RPC methods of an existing, established gRPC client connection on a gRPC Go server.
In theory, what I wanted would be something like
import (
"fmt"
"net"
pb "google.golang.org/grpc/examples/helloworld/helloworld"
"google.golang.org/grpc"
)
func server(port int, client pb.GreeterClient) {
lis, err := net.Listen("tcp", fmt.Sprintf(":%d", port))
if err != nil {
panic(err)
}
s := grpc.NewServer()
// this fails:
// cannot use client (variable of type helloworld.GreeterClient) as helloworld.GreeterServer
// have SayHello(context.Context, *helloworld.HelloRequest, ...grpc.CallOption) (*helloworld.HelloReply, error)
// want SayHello(context.Context, *helloworld.HelloRequest) (*helloworld.HelloReply, error)
pb.RegisterGreeterServer(s, client)
// other registrations specific to this server
if err := s.Serve(lis); err != nil {
panic(err)
}
}
I could create a wrapper, encapsulating the client and mapping each request to the corresponding client method. But as there are many methods, and they might also vary over time, I'd prefer a solution without additional boilerplate, if possible.
Do you see any straightforward alternative to use the client as server implementation?
A gRPC client and server are fundamentally different, including the method signatures. Your best bet is to implement a server and set the client as its dependency. Assuming that the request and response types are identical:
type ProxyGreeter struct {
client pb.GreeterClient
}
func (p *ProxyGreeter) SayHello(ctx context.Context, req *helloworld.HelloRequest) (*helloworld.HelloReply, error) {
return p.client.SayHello(ctx, req)
}
You should also remember to transform the server's incoming context into a client's outgoing context. That can be done with a middleware on the client. In a very simplified form:
func UnaryCtxClientInterceptor(ctx context.Context, method string, req, reply interface{}, cc *grpc.ClientConn, invoker grpc.UnaryInvoker, opts ...grpc.CallOption) error {
md, ok := metadata.FromIncomingContext(ctx)
if !ok {
// handle no incoming MD
}
outctx, err := metadata.NewOutgoingContext(ctx, md), nil
// handle err
return invoker(outctx, method, req, reply, cc, opts...)
}
And then use it when you create the client as:
grpc.WithChainUnaryInterceptor(UnaryCtxClientInterceptor)
Otherwise you can look into third-party libraries that provide gRPC reverse proxy.