proxy

package
v0.0.0-...-bd9cae9 Latest Latest
Warning

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

Go to latest
Published: Apr 14, 2024 License: Apache-2.0 Imports: 9 Imported by: 0

Documentation

Overview

Package 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 deprecated

func Codec() grpc.Codec

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

See CodecWithParent.

Deprecated: No longer necessary.

func CodecWithParent deprecated

func CodecWithParent(fallback grpc.Codec) grpc.Codec

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

Deprecated: No longer necessary.

func DefaultProxyOpt

func DefaultProxyOpt(cc *grpc.ClientConn) grpc.ServerOption

DefaultProxyOpt returns an grpc.UnknownServiceHandler with a DefaultDirector.

func NewProxy

func NewProxy(dst *grpc.ClientConn, opts ...grpc.ServerOption) *grpc.Server

NewProxy sets up a simple proxy that forwards all requests to dst.

Example
package main

import (
	"log"

	"github.com/drauschenbach/grpc-proxy/proxy"

	"google.golang.org/grpc"
)

func main() {
	dst, err := grpc.Dial("example.com")
	if err != nil {
		log.Fatalf("dialing example.org: %v", err)
	}
	proxy := proxy.NewProxy(dst)
	_ = proxy
}
Output:

func RegisterService

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

RegisterService 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 generated pb.go file.

Example
package main

import (
	"github.com/drauschenbach/grpc-proxy/proxy"

	"google.golang.org/grpc"
)

var director proxy.StreamDirector

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

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`.

Example
package main

import (
	"github.com/drauschenbach/grpc-proxy/proxy"

	"google.golang.org/grpc"
)

var director proxy.StreamDirector

func main() {
	grpc.NewServer(
		grpc.UnknownServiceHandler(proxy.TransparentHandler(director)))
}
Output:

Types

type StreamDirector

type StreamDirector func(ctx context.Context, fullMethodName string) (context.Context, *grpc.ClientConn, error)

StreamDirector returns a gRPC ClientConn to be used to forward the call to.

The presence of the `Context` allows for rich filtering, e.g. based on Metadata (headers). If no handling is meant to be done, a `codes.NotImplemented` gRPC error should be returned.

The context returned from this function should be the context for the *outgoing* (to backend) call. In case you want to forward any Metadata between the inbound request and outbound requests, you should do it manually. However, you *must* propagate the cancel function (`context.WithCancel`) of the inbound context to the one returned.

It is worth noting that the StreamDirector will be fired *after* all server-side stream interceptors are invoked. So decisions around authorization, monitoring etc. are better to be handled there.

See the rather rich example.

Example

Provides a 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 (
	"context"
	"strings"

	"github.com/drauschenbach/grpc-proxy/proxy"

	"google.golang.org/grpc"
	"google.golang.org/grpc/codes"
	"google.golang.org/grpc/metadata"
	"google.golang.org/grpc/status"
)

var director 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, status.Errorf(codes.Unimplemented, "Unknown method")
		}
		md, ok := metadata.FromIncomingContext(ctx)
		// Copy the inbound metadata explicitly.
		outCtx := metadata.NewOutgoingContext(ctx, 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")
				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")
				return outCtx, conn, err
			}
		}
		return nil, nil, status.Errorf(codes.Unimplemented, "Unknown method")
	}
}
Output:

func DefaultDirector

func DefaultDirector(cc *grpc.ClientConn) StreamDirector

DefaultDirector returns a very simple forwarding StreamDirector that forwards all calls.

Jump to

Keyboard shortcuts

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