|
1 | 1 | # gRPC Proxy |
2 | 2 |
|
3 | | -This is an implementation of a [gRPC](http://www.grpc.io/) Proxying Server in Golang, based on [grpc-go](https://github.com/grpc/grpc-go). Features: |
4 | | - |
5 | | - * full support for all Streams: Unitary RPCs and Streams: One-Many, Many-One, Many-Many |
6 | | - * pass-through mode: no overhead of encoding/decoding messages |
7 | | - * customizable `StreamDirector` routing based on `context.Context` of the `Stream`, allowing users to return |
8 | | - a `grpc.ClientConn` after dialing the backend of choice based on: |
9 | | - - inspection of service and method name |
10 | | - - inspection of user credentials in `authorization` header |
11 | | - - inspection of custom user-features |
12 | | - - inspection of TLS client cert credentials |
13 | | - * integration tests |
14 | | - |
15 | | -## Example Use |
16 | | - |
17 | | -```go |
| 3 | +[](https://travis-ci.org/mwitkow/go-proxy) |
| 4 | +[](https://goreportcard.com/report/github.com/mwitkow/go-proxy) |
| 5 | +[](https://godoc.org/github.com/mwitkow/go-proxy) |
| 6 | +[](LICENSE) |
18 | 7 |
|
19 | | -director := func(ctx context.Context) (*grpc.ClientConn, error) { |
20 | | - if err := CheckBearerToken(ctx); err != nil { |
21 | | - return nil, grpc.Errorf(codes.PermissionDenied, "unauthorized access: %v", err) |
22 | | - } |
23 | | - stream, _ := transport.StreamFromContext(ctx) |
24 | | - backend, found := PreDialledBackends[stream.Method()]; |
25 | | - if !found { |
26 | | - return nil, grpc.Errorf(codes.Unimplemented, "the service %v is not implemented", stream.Method) |
27 | | - } |
28 | | - return backend, nil |
29 | | -} |
| 8 | +[gRPC Go](https://github.com/grpc/grpc-go) Proxy server |
30 | 9 |
|
31 | | -proxy := grpcproxy.NewProxy(director) |
32 | | -proxy.Server(boundListener) |
33 | | -``` |
| 10 | +## Project Goal |
34 | 11 |
|
35 | | -## Status |
| 12 | +Build a transparent reverse proxy for gRPC targets that will make it easy to expose gRPC services |
| 13 | +over the internet. This includes: |
| 14 | + * no needed knowledge of the semantics of requests exchanged in the call (independent rollouts) |
| 15 | + * easy, declarative definition of backends and their mappings to frontends |
| 16 | + * simple round-robin load balancing of inbound requests from a single connection to multiple backends |
36 | 17 |
|
37 | | -This is *alpha* software, written as a proof of concept. It has been integration-tested, but please expect bugs. |
| 18 | +The project now exists as a **proof of concept**, with the key piece being the `proxy` package that |
| 19 | +is a generic gRPC reverse proxy handler. |
38 | 20 |
|
39 | | -The current implementation depends on a public interface to `ClientConn.Picker()`, which hopefully will be upstreamed in [grpc-go#397](https://github.com/grpc/grpc-go/pull/397). |
40 | | - |
| 21 | +## Proxy Handler |
41 | 22 |
|
42 | | -## Contributors |
| 23 | +The package [`proxy`](proxy/) contains a generic gRPC reverse proxy handler that allows a gRPC server to |
| 24 | +not know about registered handlers or their data types. Please consult the docs, here's an exaple usage. |
43 | 25 |
|
44 | | -Names in no particular order: |
| 26 | +Defining a `StreamDirector` that decides where (if at all) to send the request |
| 27 | +```go |
| 28 | +director = func(ctx context.Context, fullMethodName string) (*grpc.ClientConn, error) { |
| 29 | + // Make sure we never forward internal services. |
| 30 | + if strings.HasPrefix(fullMethodName, "/com.example.internal.") { |
| 31 | + return nil, grpc.Errorf(codes.Unimplemented, "Unknown method") |
| 32 | + } |
| 33 | + md, ok := metadata.FromContext(ctx) |
| 34 | + if ok { |
| 35 | + // Decide on which backend to dial |
| 36 | + if val, exists := md[":authority"]; exists && val[0] == "staging.api.example.com" { |
| 37 | + // Make sure we use DialContext so the dialing can be cancelled/time out together with the context. |
| 38 | + return grpc.DialContext(ctx, "api-service.staging.svc.local", grpc.WithCodec(proxy.Codec())) |
| 39 | + } else if val, exists := md[":authority"]; exists && val[0] == "api.example.com" { |
| 40 | + return grpc.DialContext(ctx, "api-service.prod.svc.local", grpc.WithCodec(proxy.Codec())) |
| 41 | + } |
| 42 | + } |
| 43 | + return nil, grpc.Errorf(codes.Unimplemented, "Unknown method") |
| 44 | +} |
| 45 | +``` |
| 46 | +Then you need to register it with a `grpc.Server`. The server may have other handlers that will be served |
| 47 | +locally: |
45 | 48 |
|
46 | | -* [mwitkow](https://github.com/mwitkow) |
| 49 | +```go |
| 50 | +server := grpc.NewServer( |
| 51 | + grpc.CustomCodec(proxy.Codec()), |
| 52 | + grpc.UnknownServiceHandler(proxy.TransparentHandler(director))) |
| 53 | +pb_test.RegisterTestServiceServer(server, &testImpl{}) |
| 54 | +``` |
47 | 55 |
|
48 | 56 | ## License |
49 | 57 |
|
50 | | -`grpc-proxy` is released under the Apache 2.0 license. See [LICENSE.txt](https://github.com/spf13/mwitkow-io/blob/grpcproxy/LICENSE.txt). |
51 | | - |
| 58 | +`grpc-proxy` is released under the Apache 2.0 license. See [LICENSE.txt](LICENSE.txt). |
52 | 59 |
|
53 | | -Part of the main server loop are lifted from the [grpc-go](https://github.com/grpc/grpc-go) `Server`, which is copyrighted Google Inc. and licensed under MIT license. |
|
0 commit comments