Skip to content

Commit

Permalink
move to proxy subdirectory
Browse files Browse the repository at this point in the history
  • Loading branch information
Michal Witkowski committed Feb 28, 2017
1 parent 4889d78 commit f457856
Show file tree
Hide file tree
Showing 13 changed files with 307 additions and 136 deletions.
13 changes: 13 additions & 0 deletions .travis.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
sudo: false
language: go
go:
- 1.7
- 1.8

install:
- go get google.golang.org/grpc
- go get golang.org/x/net/context
- go get github.com/stretchr/testify

script:
- go test -race -v ./...
22 changes: 0 additions & 22 deletions director.go

This file was deleted.

11 changes: 0 additions & 11 deletions patch/get_transport.go

This file was deleted.

83 changes: 83 additions & 0 deletions proxy/DOC.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
# proxy
--
import "github.com/mwitkow/grpc-proxy/proxy"

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.

## Usage

#### func Codec

```go
func Codec() grpc.Codec
```
Codec returns a proxying grpc.Codec with the default protobuf codec as parent.

See CodecWithParent.

#### func CodecWithParent

```go
func CodecWithParent(fallback grpc.Codec) grpc.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 RegisterService

```go
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 codegenerated pb.go file.

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

#### func TransparentHandler

```go
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 grpcproxy.CodecForServer()
ServerOption.

#### type StreamDirector

```go
type StreamDirector func(ctx context.Context, fullMethodName string) (*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.

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.
1 change: 1 addition & 0 deletions proxy/README.md
70 changes: 70 additions & 0 deletions proxy/codec.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
package proxy

import (
"fmt"

"github.com/golang/protobuf/proto"
"google.golang.org/grpc"
)

// Codec returns a proxying grpc.Codec with the default protobuf codec as parent.
//
// See CodecWithParent.
func Codec() grpc.Codec {
return CodecWithParent(&protoCodec{})
}

// 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 CodecWithParent(fallback grpc.Codec) grpc.Codec {
return &rawCodec{fallback}
}

type rawCodec struct {
parentCodec grpc.Codec
}

type frame struct {
payload []byte
}

func (c *rawCodec) Marshal(v interface{}) ([]byte, error) {
out, ok := v.(*frame)
if !ok {
return c.parentCodec.Marshal(v)
}
return out.payload, nil

}

func (c *rawCodec) Unmarshal(data []byte, v interface{}) error {
dst, ok := v.(*frame)
if !ok {
return c.parentCodec.Unmarshal(data, v)
}
dst.payload = data
return nil
}

func (c *rawCodec) String() string {
return fmt.Sprintf("proxy>%s", c.parentCodec.String())
}

// protoCodec is a Codec implementation with protobuf. It is the default rawCodec for gRPC.
type protoCodec struct{}

func (protoCodec) Marshal(v interface{}) ([]byte, error) {
return proto.Marshal(v.(proto.Message))
}

func (protoCodec) Unmarshal(data []byte, v interface{}) error {
return proto.Unmarshal(data, v.(proto.Message))
}

func (protoCodec) String() string {
return "proto"
}
5 changes: 3 additions & 2 deletions proxy_codec_test.go → proxy/codec_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,14 @@ package proxy

import (
"testing"

"github.com/stretchr/testify/require"
)

func TestProxyCodec_ReadYourWrites(t *testing.T) {
func TestCodec_ReadYourWrites(t *testing.T) {
framePtr := &frame{}
data := []byte{0xDE, 0xAD, 0xBE, 0xEF}
codec := codec{}
codec := rawCodec{}
require.NoError(t, codec.Unmarshal(data, framePtr), "unmarshalling must go ok")
out, err := codec.Marshal(framePtr)
require.NoError(t, err, "no marshal error")
Expand Down
20 changes: 20 additions & 0 deletions proxy/director.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
// Copyright 2017 Michal Witkowski. All Rights Reserved.
// See LICENSE for licensing terms.

package proxy

import (
"golang.org/x/net/context"
"google.golang.org/grpc"
)

// 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.
//
// 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.
type StreamDirector func(ctx context.Context, fullMethodName string) (*grpc.ClientConn, error)
15 changes: 15 additions & 0 deletions proxy/doc.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
// Copyright 2017 Michal Witkowski. All Rights Reserved.
// See LICENSE for licensing terms.

/*
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.
*/
package proxy
55 changes: 55 additions & 0 deletions proxy/examples_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
// Copyright 2017 Michal Witkowski. All Rights Reserved.
// See LICENSE for licensing terms.

package proxy_test

import (
"strings"

"github.com/mwitkow/grpc-proxy/proxy"
"golang.org/x/net/context"
"google.golang.org/grpc"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/metadata"
)

var (
director proxy.StreamDirector
)

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

func ExampleTransparentHandler() {
grpc.NewServer(
grpc.CustomCodec(proxy.Codec()),
grpc.UnknownServiceHandler(proxy.TransparentHandler(director)))
}

// 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.
func ExampleStreamDirector() {
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")
}
}
Loading

0 comments on commit f457856

Please sign in to comment.