grpc_proxy

package module
v0.0.0-...-2b06b5c Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: May 22, 2021 License: Apache-2.0 Imports: 8 Imported by: 0

README

gRPC Proxy

Travis Build Go Report Card GoDoc Apache 2.0 License

gRPC Go Proxy server

Project Goal

Build a transparent reverse proxy for gRPC targets that will make it easy to expose gRPC services over the internet. This includes:

  • no needed knowledge of the semantics of requests exchanged in the call (independent rollouts)
  • easy, declarative definition of backends and their mappings to frontends
  • simple round-robin load balancing of inbound requests from a single connection to multiple backends

The project now exists as a proof of concept, with the key piece being the proxy package that is a generic gRPC reverse proxy handler.

Proxy Handler

The package proxy contains a generic gRPC reverse proxy handler that allows a gRPC server to not know about registered handlers or their data types. Please consult the docs, here's an exaple usage.

Defining a StreamDirector that decides where (if at all) to send the request

director = func(ctx context.Context, fullMethodName string) (*grpc.ClientConn, error) {
    // Make sure we never forward internal services.
    if strings.HasPrefix(fullMethodName, "/com.example.internal.") {
        return nil, grpc.Errorf(codes.Unimplemented, "Unknown method")
    }
    md, ok := metadata.FromContext(ctx)
    if ok {
        // Decide on which backend to dial
        if val, exists := md[":authority"]; exists && val[0] == "staging.api.example.com" {
            // Make sure we use DialContext so the dialing can be cancelled/time out together with the context.
            return grpc.DialContext(ctx, "api-service.staging.svc.local", grpc.WithCodec(proxy.Codec()))
        } else if val, exists := md[":authority"]; exists && val[0] == "api.example.com" {
            return grpc.DialContext(ctx, "api-service.prod.svc.local", grpc.WithCodec(proxy.Codec()))
        }
    }
    return nil, grpc.Errorf(codes.Unimplemented, "Unknown method")
}

Then you need to register it with a grpc.Server. The server may have other handlers that will be served locally:

server := grpc.NewServer(
    grpc.CustomCodec(proxy.Codec()),
    grpc.UnknownServiceHandler(proxy.TransparentHandler(director)))
pb_test.RegisterTestServiceServer(server, &testImpl{})

License

grpc-proxy is released under the Apache 2.0 license. See LICENSE.txt.

Documentation

Overview

Package grpc_proxy provides a reverse proxy handler for gRPC.

The implementation allows a `grpc.Server` to pass a received ServerStream to a ClientStream without understanding the semantics of the messages exchanged. It basically provides a transparent reverse-proxy.

This package is intentionally generic, exposing a `StreamDirector` function that allows users of this package to implement whatever logic of backend-picking, dialing and service verification to perform.

See examples on documented functions.

Index

Examples

Constants

This section is empty.

Variables

This section is empty.

Functions

func Codec

func Codec() encoding.Codec

Codec returns a proxying grpc.Codec with the default protobuf codec as parent.

See CodecWithParent.

func CodecWithParent

func CodecWithParent(fallback encoding.Codec) encoding.Codec

CodecWithParent returns a proxying grpc.Codec with a user provided codec as parent.

This codec is *crucial* to the functioning of the proxy. It allows the proxy server to be oblivious to the schema of the forwarded messages. It basically treats a gRPC message frame as raw bytes. However, if the server handler, or the client caller are not proxy-internal functions it will fall back to trying to decode the message using a fallback codec.

func HijackProtoCodec

func HijackProtoCodec()

Replace default 'proto' codec with ours

func RegisterService

func RegisterService(server *grpc.Server, director StreamDirector, desc grpc.ServiceDesc)
Example
package main

import (
	"google.golang.org/grpc"

	"github.com/themakers/grpc_proxy"
)

var director grpc_proxy.StreamDirector

func main() {
	// A gRPC server with the proxying codec enabled.
	server := grpc.NewServer(grpc.CustomCodec(grpc_proxy.Codec()))
	// Register a TestService with 4 of its methods explicitly.
	grpc_proxy.RegisterService(server, director,
		"mwitkow.testproto.TestService",
		"PingEmpty", "Ping", "PingError", "PingList")
}
Output:

func RegisterServiceMethods

func RegisterServiceMethods(server *grpc.Server, director StreamDirector, serviceName string, methodNames ...string)

RegisterServiceMethods sets up a proxy handler for a particular gRPC service and method. The behaviour is the same as if you were registering a handler method, e.g. from a codegenerated pb.go file.

This can *only* be used if the `server` also uses grpcproxy.CodecForServer() ServerOption.

func TransparentHandler

func TransparentHandler(director StreamDirector) grpc.StreamHandler

TransparentHandler returns a handler that attempts to proxy all requests that are not registered in the server. The indented use here is as a transparent proxy, where the server doesn't know about the services implemented by the backends. It should be used as a `grpc.UnknownServiceHandler`.

This can *only* be used if the `server` also uses grpc_proxy.CodecForServer() ServerOption.

Example
package main

import (
	"google.golang.org/grpc"

	"github.com/themakers/grpc_proxy"
)

var director grpc_proxy.StreamDirector

func main() {
	grpc.NewServer(
		grpc.CustomCodec(grpc_proxy.Codec()),
		grpc.UnknownServiceHandler(grpc_proxy.TransparentHandler(director)))
}
Output:

Types

type StreamDirector

type StreamDirector interface {
	// Connect returns a connection to use for the given method,
	// or an error if the call should not be handled.
	//
	// The provided context may be inspected for filtering on request
	// metadata.
	//
	// Method is the gRPC request path, which is in the form "/service/method".
	//
	// The returned context is used as the basis for the outgoing connection.
	Connect(ctx context.Context, method string) (context.Context, *grpc.ClientConn, error)

	// Release is called when a connection is longer being used.  This is called
	// once for every call to Connect that does not return an error.
	//
	// The provided context is the one returned from Connect.
	//
	// This can be used by the director to pool connections or close unused
	// connections.
	Release(ctx context.Context, conn *grpc.ClientConn)
}
Example

Provide sa simple example of a director that shields internal services and dials a staging or production backend. This is a *very naive* implementation that creates a new connection on every request. Consider using pooling.

package main

import (
	"strings"

	"golang.org/x/net/context"
	"google.golang.org/grpc"
	"google.golang.org/grpc/codes"
	"google.golang.org/grpc/metadata"

	"github.com/themakers/grpc_proxy"
)

var director grpc_proxy.StreamDirector

func main() {
	director = func(ctx context.Context, fullMethodName string) (context.Context, *grpc.ClientConn, error) {
		// Make sure we never forward internal services.
		if strings.HasPrefix(fullMethodName, "/com.example.internal.") {
			return nil, nil, grpc.Errorf(codes.Unimplemented, "Unknown method")
		}
		md, ok := metadata.FromIncomingContext(ctx)
		// Copy the inbound metadata explicitly.
		outCtx, _ := context.WithCancel(ctx)
		outCtx = metadata.NewOutgoingContext(outCtx, md.Copy())
		if ok {
			// Decide on which backend to dial
			if val, exists := md[":authority"]; exists && val[0] == "staging.api.example.com" {
				// Make sure we use DialContext so the dialing can be cancelled/time out together with the context.
				conn, err := grpc.DialContext(ctx, "api-service.staging.svc.local", grpc.WithCodec(grpc_proxy.Codec()))
				return outCtx, conn, err
			} else if val, exists := md[":authority"]; exists && val[0] == "api.example.com" {
				conn, err := grpc.DialContext(ctx, "api-service.prod.svc.local", grpc.WithCodec(grpc_proxy.Codec()))
				return outCtx, conn, err
			}
		}
		return nil, nil, grpc.Errorf(codes.Unimplemented, "Unknown method")
	}
}
Output:

Directories

Path Synopsis
Package mwitkow_testproto is a generated protocol buffer package.
Package mwitkow_testproto is a generated protocol buffer package.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL