|
1 |
| -# <img src="https://protogen.marcgravell.com/images/protobuf-net.svg" alt="protobuf-net logo" width="45" height="45"> protobuf-net.Grpc |
| 1 | +# What is protobuf-net.GrpcLite? |
2 | 2 |
|
3 |
| -[](https://ci.appveyor.com/project/StackExchange/protobuf-net-grpc/branch/main) |
| 3 | +It is a drop in protocol replacement for gRPC; the .NET gRPC API has no hard bindings to either the marshaller or the underlying transport; protobuf-net.Grpc offered ways to change the marshaller (for example, |
| 4 | +allowing you to use protobuf-net) - and now protobuf-net.GrpcLite allows you to change the transport - from HTTP/2 to a custom transport *inspired* by HTTP/2, but simpler and with lower overheads. It is also |
| 5 | +fully managed, unlike HTTP/2 which often required unmanaged library or OS support. |
4 | 6 |
|
5 |
| -`protobuf-net.Grpc` adds code-first support for services over gRPC using either the native `Grpc.Core` API, or the fully-managed `Grpc.Net.Client` / `Grpc.AspNetCore.Server` API. |
| 7 | +The transport *is not compatible* with regular HTTP/2 gRPC, but: all of your existing gRPC code should continue to function, as long as you have a client and server that can talk the same dialect. |
6 | 8 |
|
7 |
| -It should work on all .NET languages that can generate something *even remotely like* a regular .NET type model. |
| 9 | +## How do I use it? |
8 | 10 |
|
9 |
| -- [Getting Started](https://protobuf-net.github.io/protobuf-net.Grpc/gettingstarted) |
10 |
| -- [All Documentation](https://protobuf-net.github.io/protobuf-net.Grpc/) |
11 |
| -- [Build/usage available via `protobuf-net.BuildTools`](https://protobuf-net.github.io/protobuf-net/build_tools) |
12 |
| - |
13 |
| -Usage is as simple as declaring an interface for your service-contract: |
| 11 | +At the client, instead of using `var channel = new Channel(...);` (unmanaged HTTP/2) or `var channel = GrpcChannel.ForAddress(...);` (managed HTTP/2), you would use something like: |
14 | 12 |
|
15 | 13 | ``` c#
|
16 |
| -[ServiceContract] |
17 |
| -public interface IMyAmazingService { |
18 |
| - ValueTask<SearchResponse> SearchAsync(SearchRequest request); |
19 |
| - // ... |
20 |
| -} |
| 14 | +using var channel = await ConnectionFactory.ConnectSocket(endPoint).AsFrames().CreateChannelAsync(); |
21 | 15 | ```
|
22 | 16 |
|
23 |
| -then either implementing that interface for a server: |
| 17 | +The rest of your client code *shouldn't change at all*. This is just one example; other terminators are possible - for example, anything that can provide a `Stream` should work, including support |
| 18 | +for things like TLS, compression, named pipes, etc. |
| 19 | + |
| 20 | +At the server, the code is currently a bit closer to the unmanaged server implementation (the server does not integrate deeply into Kestrel, although it works fine inside a Kestrel process); service-binding |
| 21 | +is via the `.ServiceBinder`: |
24 | 22 |
|
25 | 23 | ``` c#
|
26 |
| -public class MyServer : IMyAmazingService { |
27 |
| - // ... |
28 |
| -} |
| 24 | +var server = new LiteServer(); |
| 25 | +server.ServiceBinder.Bind(new MyService()); // contract-first example, generated via protoc |
| 26 | +
|
| 27 | +// alternative if not also using protobuf-net.Grpc, which provides the Bind API |
| 28 | +// YourService.BindService(server.ServiceBinder, new MyService()); |
| 29 | +
|
| 30 | +_ = server.ListenAsync(ConnectionFactory.ListenSocket(endpoint).AsStream().AsFrames()); |
| 31 | +// ... note: leave your server running here, until you're ready to exit! |
| 32 | +server.Stop(); |
29 | 33 | ```
|
30 | 34 |
|
31 |
| -or asking the system for a client: |
| 35 | +The `ListenAsync` call will listen for multiple connections; a single server can listen to many connections on many different listeneres at once - for example, you could |
| 36 | +listen to multiple TCP ports, with/without TLS. Your `MyService` instance will be activated just like it would have been with the unmanaged server host. |
| 37 | + |
| 38 | +## How do I use TLS? |
| 39 | + |
| 40 | +TLS is provided via `SslStream`, and works with or without client certificates; the `WithTls()` connector optionally accepts callbacks for providing user certificates (client), or |
| 41 | +validating remote certificates (client or server); the `AuthenticateAsServer()` connector accepts a server certificate, and optionally demands client certificates; for example: |
| 42 | + |
| 43 | +``` c# |
| 44 | +// TCP server; no TLS |
| 45 | +_ = server.ListenAsync(ConnectionFactory.ListenSocket(endpoint).AsStream().AsFrames()); |
| 46 | +// TCP server; TLS, no client certs |
| 47 | +_ = server.ListenAsync(ConnectionFactory.ListenSocket(endpoint).AsStream().WithTls().AuthenticateAsServer(serverCert).AsFrames()); |
| 48 | +// TCP server; TLS, client certs (validated via userCheck) |
| 49 | +_ = server.ListenAsync(ConnectionFactory.ListenSocket(endpoint).AsStream().WithTls(userCheck).AuthenticateAsServer(serverCert, clientCertificateRequired: true).AsFrames()); |
| 50 | + |
32 | 51 |
|
33 | 52 | ``` c#
|
34 |
| -var client = http.CreateGrpcService<IMyAmazingService>(); |
35 |
| -var results = await client.SearchAsync(request); |
| 53 | +// TCP client; no TLS |
| 54 | +using var channel = await ConnectionFactory.ConnectSocket(endPoint).AsFrames().CreateChannelAsync(); |
| 55 | +// TCP client; TLS, using default server validation and certificate selection |
| 56 | +using var channel = await ConnectionFactory.ConnectSocket(endpoint).AsStream().WithTls().AuthenticateAsClient("mytestserver").AsFrames().CreateChannelAsync(); |
| 57 | +// TCP client; TLS, using custom server validation and certificate selection |
| 58 | +using var channel = await ConnectionFactory.ConnectSocket(endpoint).AsStream().WithTls(serverCheck, certSelector).AuthenticateAsClient("mytestserver").AsFrames().CreateChannelAsync(); |
36 | 59 | ```
|
37 | 60 |
|
38 |
| -This would be equivalent to the service in .proto: |
| 61 | +## How do I use code-first? |
| 62 | + |
| 63 | +At the client, code-first works exactly as it always has; just use the `.CreateClient<TService>()` method on the channel. |
| 64 | + |
| 65 | +As the server, binding code-first serves to the custom server is uses the `.Binder` API: |
39 | 66 |
|
40 |
| -``` proto |
41 |
| -service MyAmazingService { |
42 |
| - rpc Search (SearchRequest) returns (SearchResponse) {} |
43 |
| - // ... |
44 |
| -} |
| 67 | +``` c# |
| 68 | +server.ServiceBinder.AddCodeFirst(...); |
45 | 69 | ```
|
46 | 70 |
|
47 |
| -Obviously you need to tell it the uri etc - see [Getting Started](https://protobuf-net.github.io/protobuf-net.Grpc/gettingstarted). Usually the configuration is convention-based, but |
48 |
| -if you prefer: there are [various configuration options](https://protobuf-net.github.io/protobuf-net.Grpc/configuration). |
| 71 | +## How do I use interceptors? |
49 | 72 |
|
50 |
| -## Getting hold of it |
| 73 | +Client-side interceptors work exactly like they do in all scenarios. |
51 | 74 |
|
52 |
| -Everything is available as pre-built packages on nuget; in particular, you probably want one of: |
| 75 | +To register a server-side interceptor, the `Intercept()` API is used alongside the `.ServiceBinder`: |
53 | 76 |
|
54 |
| -- [`protobuf-net.Grpc.AspNetCore`](https://www.nuget.org/packages/protobuf-net.Grpc.AspNetCore) for servers using ASP.NET Core 3.1 |
55 |
| -- [`protobuf-net.Grpc.Native`](https://www.nuget.org/packages/protobuf-net.Grpc.Native) for clients or servers using the native/unmanaged API |
56 |
| -- [`protobuf-net.Grpc`](https://www.nuget.org/packages/protobuf-net.Grpc) and [`Grpc.Net.Client`](https://www.nuget.org/packages/Grpc.Net.Client/) for clients using `HttpClient` on .NET Core 3.1 |
| 77 | +``` c# |
| 78 | +server.ServiceBinder.Intercept(...).Bind(new MyService()); // contract-first example, generated via protoc |
| 79 | +server.ServiceBinder.Intercept(...).AddCodeFirst(...); // code-first |
57 | 80 |
|
58 |
| -[Usage examples are available in C#, VB and F#](https://github.com/protobuf-net/protobuf-net.Grpc/tree/main/examples/pb-net-grpc). |
| 81 | +// alternative for contract-first if not also using protobuf-net.Grpc, which provides the Bind API |
| 82 | +// YourService.BindService(server.ServiceBinder.Intercept(...), new MyService()); |
| 83 | +``` |
59 | 84 |
|
60 |
| -## Anything else? |
| 85 | +## Other notes |
61 | 86 |
|
62 |
| -`protobuf-net.Grpc` is created and maintained by [Marc Gravell](https://github.com/mgravell) ([@marcgravell](https://twitter.com/marcgravell)), the author of `protobuf-net`. |
| 87 | +It currently targets .NET Framework 4.7.2 up to .NET 6.0, using newer features when available. It is still very experimental - but most core things should work; feedback is welcome. |
63 | 88 |
|
64 |
| -It makes use of tools from [grpc](https://github.com/grpc/), but is not official associated with, affiliated with, or endorsed by that project. |
| 89 | +Known gaps: |
65 | 90 |
|
66 |
| -I look forward to your feedback, and if this could save you a ton of time, you're always welcome to [](https://www.buymeacoffee.com/marcgravell) |
| 91 | +- gRPC auth (although transport auth works fine) |
| 92 | +- per-stream service activation (rather than singleton) |
| 93 | +- testing needs more coverage |
| 94 | +- per-stream backoff negotiation; designed, not yet implemented |
| 95 | +- for some reason the server implementation isn't working 100% with SAEA currently - hence `.AsStream().AsFrames()` instead of just `.AsFrames()` |
| 96 | +- open question around interceptor order |
0 commit comments