Skip to content

Commit f457856

Browse files
author
Michal Witkowski
committed
move to proxy subdirectory
1 parent 4889d78 commit f457856

File tree

13 files changed

+307
-136
lines changed

13 files changed

+307
-136
lines changed

.travis.yml

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
sudo: false
2+
language: go
3+
go:
4+
- 1.7
5+
- 1.8
6+
7+
install:
8+
- go get google.golang.org/grpc
9+
- go get golang.org/x/net/context
10+
- go get github.com/stretchr/testify
11+
12+
script:
13+
- go test -race -v ./...

director.go

Lines changed: 0 additions & 22 deletions
This file was deleted.

patch/get_transport.go

Lines changed: 0 additions & 11 deletions
This file was deleted.

proxy/DOC.md

Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
# proxy
2+
--
3+
import "github.com/mwitkow/grpc-proxy/proxy"
4+
5+
Package proxy provides a reverse proxy handler for gRPC.
6+
7+
The implementation allows a `grpc.Server` to pass a received ServerStream to a
8+
ClientStream without understanding the semantics of the messages exchanged. It
9+
basically provides a transparent reverse-proxy.
10+
11+
This package is intentionally generic, exposing a `StreamDirector` function that
12+
allows users of this package to implement whatever logic of backend-picking,
13+
dialing and service verification to perform.
14+
15+
See examples on documented functions.
16+
17+
## Usage
18+
19+
#### func Codec
20+
21+
```go
22+
func Codec() grpc.Codec
23+
```
24+
Codec returns a proxying grpc.Codec with the default protobuf codec as parent.
25+
26+
See CodecWithParent.
27+
28+
#### func CodecWithParent
29+
30+
```go
31+
func CodecWithParent(fallback grpc.Codec) grpc.Codec
32+
```
33+
CodecWithParent returns a proxying grpc.Codec with a user provided codec as
34+
parent.
35+
36+
This codec is *crucial* to the functioning of the proxy. It allows the proxy
37+
server to be oblivious to the schema of the forwarded messages. It basically
38+
treats a gRPC message frame as raw bytes. However, if the server handler, or the
39+
client caller are not proxy-internal functions it will fall back to trying to
40+
decode the message using a fallback codec.
41+
42+
#### func RegisterService
43+
44+
```go
45+
func RegisterService(server *grpc.Server, director StreamDirector, serviceName string, methodNames ...string)
46+
```
47+
RegisterService sets up a proxy handler for a particular gRPC service and
48+
method. The behaviour is the same as if you were registering a handler method,
49+
e.g. from a codegenerated pb.go file.
50+
51+
This can *only* be used if the `server` also uses grpcproxy.CodecForServer()
52+
ServerOption.
53+
54+
#### func TransparentHandler
55+
56+
```go
57+
func TransparentHandler(director StreamDirector) grpc.StreamHandler
58+
```
59+
TransparentHandler returns a handler that attempts to proxy all requests that
60+
are not registered in the server. The indented use here is as a transparent
61+
proxy, where the server doesn't know about the services implemented by the
62+
backends. It should be used as a `grpc.UnknownServiceHandler`.
63+
64+
This can *only* be used if the `server` also uses grpcproxy.CodecForServer()
65+
ServerOption.
66+
67+
#### type StreamDirector
68+
69+
```go
70+
type StreamDirector func(ctx context.Context, fullMethodName string) (*grpc.ClientConn, error)
71+
```
72+
73+
StreamDirector returns a gRPC ClientConn to be used to forward the call to.
74+
75+
The presence of the `Context` allows for rich filtering, e.g. based on Metadata
76+
(headers). If no handling is meant to be done, a `codes.NotImplemented` gRPC
77+
error should be returned.
78+
79+
It is worth noting that the StreamDirector will be fired *after* all server-side
80+
stream interceptors are invoked. So decisions around authorization, monitoring
81+
etc. are better to be handled there.
82+
83+
See the rather rich example.

proxy/README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
DOC.md

proxy/codec.go

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
package proxy
2+
3+
import (
4+
"fmt"
5+
6+
"github.com/golang/protobuf/proto"
7+
"google.golang.org/grpc"
8+
)
9+
10+
// Codec returns a proxying grpc.Codec with the default protobuf codec as parent.
11+
//
12+
// See CodecWithParent.
13+
func Codec() grpc.Codec {
14+
return CodecWithParent(&protoCodec{})
15+
}
16+
17+
// CodecWithParent returns a proxying grpc.Codec with a user provided codec as parent.
18+
//
19+
// This codec is *crucial* to the functioning of the proxy. It allows the proxy server to be oblivious
20+
// to the schema of the forwarded messages. It basically treats a gRPC message frame as raw bytes.
21+
// However, if the server handler, or the client caller are not proxy-internal functions it will fall back
22+
// to trying to decode the message using a fallback codec.
23+
func CodecWithParent(fallback grpc.Codec) grpc.Codec {
24+
return &rawCodec{fallback}
25+
}
26+
27+
type rawCodec struct {
28+
parentCodec grpc.Codec
29+
}
30+
31+
type frame struct {
32+
payload []byte
33+
}
34+
35+
func (c *rawCodec) Marshal(v interface{}) ([]byte, error) {
36+
out, ok := v.(*frame)
37+
if !ok {
38+
return c.parentCodec.Marshal(v)
39+
}
40+
return out.payload, nil
41+
42+
}
43+
44+
func (c *rawCodec) Unmarshal(data []byte, v interface{}) error {
45+
dst, ok := v.(*frame)
46+
if !ok {
47+
return c.parentCodec.Unmarshal(data, v)
48+
}
49+
dst.payload = data
50+
return nil
51+
}
52+
53+
func (c *rawCodec) String() string {
54+
return fmt.Sprintf("proxy>%s", c.parentCodec.String())
55+
}
56+
57+
// protoCodec is a Codec implementation with protobuf. It is the default rawCodec for gRPC.
58+
type protoCodec struct{}
59+
60+
func (protoCodec) Marshal(v interface{}) ([]byte, error) {
61+
return proto.Marshal(v.(proto.Message))
62+
}
63+
64+
func (protoCodec) Unmarshal(data []byte, v interface{}) error {
65+
return proto.Unmarshal(data, v.(proto.Message))
66+
}
67+
68+
func (protoCodec) String() string {
69+
return "proto"
70+
}

proxy_codec_test.go renamed to proxy/codec_test.go

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,14 @@ package proxy
22

33
import (
44
"testing"
5+
56
"github.com/stretchr/testify/require"
67
)
78

8-
func TestProxyCodec_ReadYourWrites(t *testing.T) {
9+
func TestCodec_ReadYourWrites(t *testing.T) {
910
framePtr := &frame{}
1011
data := []byte{0xDE, 0xAD, 0xBE, 0xEF}
11-
codec := codec{}
12+
codec := rawCodec{}
1213
require.NoError(t, codec.Unmarshal(data, framePtr), "unmarshalling must go ok")
1314
out, err := codec.Marshal(framePtr)
1415
require.NoError(t, err, "no marshal error")

proxy/director.go

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
// Copyright 2017 Michal Witkowski. All Rights Reserved.
2+
// See LICENSE for licensing terms.
3+
4+
package proxy
5+
6+
import (
7+
"golang.org/x/net/context"
8+
"google.golang.org/grpc"
9+
)
10+
11+
// StreamDirector returns a gRPC ClientConn to be used to forward the call to.
12+
//
13+
// The presence of the `Context` allows for rich filtering, e.g. based on Metadata (headers).
14+
// If no handling is meant to be done, a `codes.NotImplemented` gRPC error should be returned.
15+
//
16+
// It is worth noting that the StreamDirector will be fired *after* all server-side stream interceptors
17+
// are invoked. So decisions around authorization, monitoring etc. are better to be handled there.
18+
//
19+
// See the rather rich example.
20+
type StreamDirector func(ctx context.Context, fullMethodName string) (*grpc.ClientConn, error)

proxy/doc.go

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
// Copyright 2017 Michal Witkowski. All Rights Reserved.
2+
// See LICENSE for licensing terms.
3+
4+
/*
5+
Package proxy provides a reverse proxy handler for gRPC.
6+
7+
The implementation allows a `grpc.Server` to pass a received ServerStream to a ClientStream without understanding
8+
the semantics of the messages exchanged. It basically provides a transparent reverse-proxy.
9+
10+
This package is intentionally generic, exposing a `StreamDirector` function that allows users of this package
11+
to implement whatever logic of backend-picking, dialing and service verification to perform.
12+
13+
See examples on documented functions.
14+
*/
15+
package proxy

proxy/examples_test.go

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
// Copyright 2017 Michal Witkowski. All Rights Reserved.
2+
// See LICENSE for licensing terms.
3+
4+
package proxy_test
5+
6+
import (
7+
"strings"
8+
9+
"github.com/mwitkow/grpc-proxy/proxy"
10+
"golang.org/x/net/context"
11+
"google.golang.org/grpc"
12+
"google.golang.org/grpc/codes"
13+
"google.golang.org/grpc/metadata"
14+
)
15+
16+
var (
17+
director proxy.StreamDirector
18+
)
19+
20+
func ExampleRegisterService() {
21+
// A gRPC server with the proxying codec enabled.
22+
server := grpc.NewServer(grpc.CustomCodec(proxy.Codec()))
23+
// Register a TestService with 4 of its methods explicitly.
24+
proxy.RegisterService(server, director,
25+
"mwitkow.testproto.TestService",
26+
"PingEmpty", "Ping", "PingError", "PingList")
27+
}
28+
29+
func ExampleTransparentHandler() {
30+
grpc.NewServer(
31+
grpc.CustomCodec(proxy.Codec()),
32+
grpc.UnknownServiceHandler(proxy.TransparentHandler(director)))
33+
}
34+
35+
// Provide sa simple example of a director that shields internal services and dials a staging or production backend.
36+
// This is a *very naive* implementation that creates a new connection on every request. Consider using pooling.
37+
func ExampleStreamDirector() {
38+
director = func(ctx context.Context, fullMethodName string) (*grpc.ClientConn, error) {
39+
// Make sure we never forward internal services.
40+
if strings.HasPrefix(fullMethodName, "/com.example.internal.") {
41+
return nil, grpc.Errorf(codes.Unimplemented, "Unknown method")
42+
}
43+
md, ok := metadata.FromContext(ctx)
44+
if ok {
45+
// Decide on which backend to dial
46+
if val, exists := md[":authority"]; exists && val[0] == "staging.api.example.com" {
47+
// Make sure we use DialContext so the dialing can be cancelled/time out together with the context.
48+
return grpc.DialContext(ctx, "api-service.staging.svc.local", grpc.WithCodec(proxy.Codec()))
49+
} else if val, exists := md[":authority"]; exists && val[0] == "api.example.com" {
50+
return grpc.DialContext(ctx, "api-service.prod.svc.local", grpc.WithCodec(proxy.Codec()))
51+
}
52+
}
53+
return nil, grpc.Errorf(codes.Unimplemented, "Unknown method")
54+
}
55+
}

0 commit comments

Comments
 (0)