From 28e66efa2a3c2e862318e02c53abaa0d7b9562c2 Mon Sep 17 00:00:00 2001 From: Juan Alvarez Date: Sun, 29 Sep 2019 23:16:25 -0500 Subject: [PATCH 01/29] started routeguide tutorial --- tonic-examples/routeguide-tutorial.md | 637 ++++++++++++++++++++++++++ 1 file changed, 637 insertions(+) create mode 100644 tonic-examples/routeguide-tutorial.md diff --git a/tonic-examples/routeguide-tutorial.md b/tonic-examples/routeguide-tutorial.md new file mode 100644 index 000000000..28796d547 --- /dev/null +++ b/tonic-examples/routeguide-tutorial.md @@ -0,0 +1,637 @@ +# gRPC Basics: Tonic + +This tutorial, adapted from [grpc-go][grpc-go], provides a basic introduction to working with gRPC +and tonic. By walking through this example you'll learn how to: + +- Define a service in a `.proto` file. +- Generate server and client code. +- Write a simple client and server for your service. + +It assumes you are familiar with [protocol buffers][protobuf] and Rust. Note that the example in +this tutorial uses the proto3 version of the protocol buffers language, you can find out more in the +[proto3 language guide][proto3]. + +[grpc-go]: https://github.com/grpc/grpc-go/blob/master/examples/gotutorial.md +[protobuf]: https://developers.google.com/protocol-buffers/docs/overview +[proto3]: https://developers.google.com/protocol-buffers/docs/proto3 + +## Why use gRPC? + +Our example is a simple route mapping application that lets clients get information about features +on their route, create a summary of their route, and exchange route information such as traffic +updates with the server and other clients. + +With gRPC we can define our service once in a `.proto` file and implement clients and servers in +any of gRPC's supported languages, which in turn can be run in environments ranging from servers +inside Google to your own tablet - all the complexity of communication between different languages +and environments is handled for you by gRPC. We also get all the advantages of working with +protocol buffers, including efficient serialization, a simple IDL, and easy interface updating. + +## Prerequisites + +To run the sample code and walk through the tutorial, the only prerequisite is Rust itself. +[rustup][rustup] is a convenient tool to install it, if you haven't already. + +[rustup]: https://rustup.rs + +## Running the example + +Clone or download tonic's repository: + +```shell +git clone https://github.com/LucioFranco/tonic.git +``` + +Change your current directory to tonic's repository root: +```shell +$ cd tonic +``` + +Tonic uses rustfmt to tidy up the code it generates, make sure it's installed. + +```shell +$ rustup component add rustfmt +``` + +Run the server +```shell +$ cargo run --bin routeguide-server +``` + +In a separate shell, run the client +```shell +$ cargo run --bin routeguide-client +``` + +**Note:** Prior to rust's 1.39 release, tonic may be pinned to a specific toolchain version. + +## Project setup + +We will develop our example from scratch in a new crate: + +```shell +$ cargo new routeguide +$ cd routeguide +``` + + +## Defining the service + +Our first step is to define the gRPC *service* and the method *request* and *response* types using +[protocol buffers][protobuf]. We will keep our `.proto` files in a directory in our crate's root. +Note that Tonic does not really care where our `.proto` definitions live. We will see how to use +different code generation configuration later in the tutorial. + + +```shell +$ mkdir proto && touch proto/route_guide.proto +``` + +You can see the complete `.proto` file in +[tonic-examples/proto/routeguide/route_guide.proto][routeguide-proto]. + +[routeguide-proto]: https://github.com/LucioFranco/tonic/blob/master/tonic-examples/proto/routeguide/route_guide.proto + +To define a service, you specify a named `service` in your `.proto` file: + +```proto +service RouteGuide { + ... +} +``` + +Then you define `rpc` methods inside your service definition, specifying their request and response +types. gRPC lets you define four kinds of service method, all of which are used in the `RouteGuide` +service: + +- A *simple RPC* where the client sends a request to the server using the stub and waits for a +response to come back, just like a normal function call. +```proto + // Obtains the feature at a given position. + rpc GetFeature(Point) returns (Feature) {} +``` + +- A *server-side streaming RPC* where the client sends a request to the server and gets a stream +to read a sequence of messages back. The client reads from the returned stream until there are +no more messages. As you can see in our example, you specify a server-side streaming method by +placing the `stream` keyword before the *response* type. +```proto + // Obtains the Features available within the given Rectangle. Results are + // streamed rather than returned at once (e.g. in a response message with a + // repeated field), as the rectangle may cover a large area and contain a + // huge number of features. + rpc ListFeatures(Rectangle) returns (stream Feature) {} +``` + +- A *client-side streaming RPC* where the client writes a sequence of messages and sends them to +the server, again using a provided stream. Once the client has finished writing the messages, +it waits for the server to read them all and return its response. You specify a client-side +streaming method by placing the `stream` keyword before the *request* type. +```proto + // Accepts a stream of Points on a route being traversed, returning a + // RouteSummary when traversal is completed. + rpc RecordRoute(stream Point) returns (RouteSummary) {} +``` + +- A *bidirectional streaming RPC* where both sides send a sequence of messages using a read-write +stream. The two streams operate independently, so clients and servers can read and write in whatever +order they like: for example, the server could wait to receive all the client messages before +writing its responses, or it could alternately read a message then write a message, or some other +combination of reads and writes. The order of messages in each stream is preserved. You specify +this type of method by placing the `stream` keyword before both the request and the response. +```proto + // Accepts a stream of RouteNotes sent while a route is being traversed, + // while receiving other RouteNotes (e.g. from other users). + rpc RouteChat(stream RouteNote) returns (stream RouteNote) {} +``` + +Our `.proto` file also contains protocol buffer message type definitions for all the request and +response types used in our service methods - for example, here's the `Point` message type: +```proto +// Points are represented as latitude-longitude pairs in the E7 representation +// (degrees multiplied by 10**7 and rounded to the nearest integer). +// Latitudes should be in the range +/- 90 degrees and longitude should be in +// the range +/- 180 degrees (inclusive). +message Point { + int32 latitude = 1; + int32 longitude = 2; +} +``` + + +## Generating client and server code + +Tonic can be configured to generate code as part cargo's normal build process. This is very +convenient because once we've set everything up, there is no extra step to keep the generated code +and our `.proto` definitions in sync. + +Behind the scenes, Tonic uses [PROST!][prost] to handle protocol buffer serialization and code +generation. + +Edit `Cargo.toml` to add all the dependencies we'll need for this example: + +```toml +[dependencies] +tonic = { path = "../tonic/tonic" } # TODO: update once there is a released version +futures-preview = { version = "=0.3.0-alpha.18", default-features = false, features = ["alloc"]} +tokio = "=0.2.0-alpha.4" +prost = "0.5" +bytes = "0.4" +serde_json = "1.0" +serde = { version = "1.0", features = ["derive"] } + +[build-dependencies] +tonic-build = { path = "../tonic/tonic-build" } # TODO: update to released version +``` + +Create a `build.rs` file at the root of your crate: + +```rust +fn main() { + tonic_build::compile_protos("proto/route_guide.proto").unwrap(); +} +``` + +[prost]: https://github.com/danburkert/prost + +```shell +$ cargo build +``` + +That's it. The generated code contains: + +- Struct definitions for message types `Point`, `Rectangle`, `Feature`, `RouteNote`, `RouteSummary`. +- A service trait we'll need to implement: `server::RouteGuide`. +- A client type we'll use to call the server: `client::RouteGuideClient`. + +If your are curious as to where the generated files are, keep reading. The mystery will be revealed. +We can now move on to the fun part. + +## Creating the server + +First let's look at how we create a `RouteGuide` server. If you're only interested in creating gRPC +clients, you can skip this section and go straight to [Creating the client](#client) +(though you might find it interesting anyway!). + +There are two parts to making our `RouteGuide` service do its job: + +- Implementing the service trait generated from our service definition. +- Running a gRPC server to listen for requests from clients. + +You can find our example `RouteGuide` server in +[tonic-examples/src/routeguide/server.rs][routeguide-server] + +[routeguide-server]: https://github.com/LucioFranco/tonic/blob/master/tonic-examples/src/routeguide/server.rs + +### Implementing the server::RouteGuide trait + +We can start by defining a struct to represent our service, we can do this on `main.rs` for now: + +```rust +#[derive(Debug)] +struct RouteGuide; +``` + +We now need to implement the `server::RouteGuide` trait that is generated in our build step. +The generated code is placed inside our target directory, in a location defined by the `OUT_DIR` +environment variable that is set by cargo. For our example, this means you can find the generated +code in a path similar to `target/debug/build/routeguide/out/routeguide.rs`. + +You can read more about the `OUT_DIR` variable in the [cargo book][cargo-book]. + +We can bring this code into scope like this: + +```rust +pub mod routeguide { + include!(concat!(env!("OUT_DIR"), "/routeguide.rs")); +} + +use routeguide::{server, Feature, Point, Rectangle, RouteNote, RouteSummary}; +``` + +[cargo-book]: https://doc.rust-lang.org/cargo/reference/environment-variables.html#environment-variables-cargo-sets-for-build-scripts + +Now we are ready to stub out our service: + +```rust +#[tonic::async_trait] +impl server::RouteGuide for RouteGuide { + async fn get_feature(&self, _request: Request) -> Result, Status> { + unimplemented!() + } + + type ListFeaturesStream = mpsc::Receiver>; + + async fn list_features( + &self, + _request: Request, + ) -> Result, Status> { + unimplemented!() + } + + async fn record_route( + &self, + _request: Request>, + ) -> Result, Status> { + unimplemented!() + } + + type RouteChatStream = Pin> + Send + 'static>>; + + async fn route_chat( + &self, + _request: Request>, + ) -> Result, Status> { + unimplemented!() + } +} +``` + +**Note**: The `tonic::async_trait` attribute macro adds support for async fn in traits. It uses +[async-trait][async-trait] internally. + +[async-trait]: https://github.com/dtolnay/async-trait + +#### Simple RPC +`routeGuideServer` implements all our service methods. Let's look at the simplest type +first, `GetFeature`, which just gets a `Point` from the client and returns the corresponding +feature information from its database in a `Feature`. + +```go +func (s *routeGuideServer) GetFeature(ctx context.Context, point *pb.Point) (*pb.Feature, error) { + for _, feature := range s.savedFeatures { + if proto.Equal(feature.Location, point) { + return feature, nil + } + } + // No feature was found, return an unnamed feature + return &pb.Feature{"", point}, nil +} +``` + +The method is passed a context object for the RPC and the client's `Point` protocol buffer request. +It returns a `Feature` protocol buffer object with the response information and an `error`. In the +method we populate the `Feature` with the appropriate information, and then `return` it along with +an `nil` error to tell gRPC that we've finished dealing with the RPC and that the `Feature` can be +returned to the client. + +#### Server-side streaming RPC +Now let's look at one of our streaming RPCs. `ListFeatures` is a server-side streaming RPC, so we +need to send back multiple `Feature`s to our client. + +```go +func (s *routeGuideServer) ListFeatures(rect *pb.Rectangle, stream pb.RouteGuide_ListFeaturesServer) error { + for _, feature := range s.savedFeatures { + if inRange(feature.Location, rect) { + if err := stream.Send(feature); err != nil { + return err + } + } + } + return nil +} +``` + +As you can see, instead of getting simple request and response objects in our method parameters, +this time we get a request object (the `Rectangle` in which our client wants to find `Feature`s) +and a special `RouteGuide_ListFeaturesServer` object to write our responses. + +In the method, we populate as many `Feature` objects as we need to return, writing them to the +`RouteGuide_ListFeaturesServer` using its `Send()` method. Finally, as in our simple RPC, we return +a `nil` error to tell gRPC that we've finished writing responses. Should any error happen in this +call, we return a non-`nil` error; the gRPC layer will translate it into an appropriate RPC status +to be sent on the wire. + +#### Client-side streaming RPC +Now let's look at something a little more complicated: the client-side streaming method +`RecordRoute`, where we get a stream of `Point`s from the client and return a single +`RouteSummary` with information about their trip. As you can see, this time the method doesn't +have a request parameter at all. Instead, it gets a `RouteGuide_RecordRouteServer` stream, which +the server can use to both read *and* write messages - it can receive client messages using its +`Recv()` method and return its single response using its `SendAndClose()` method. + +```go +func (s *routeGuideServer) RecordRoute(stream pb.RouteGuide_RecordRouteServer) error { + var pointCount, featureCount, distance int32 + var lastPoint *pb.Point + startTime := time.Now() + for { + point, err := stream.Recv() + if err == io.EOF { + endTime := time.Now() + return stream.SendAndClose(&pb.RouteSummary{ + PointCount: pointCount, + FeatureCount: featureCount, + Distance: distance, + ElapsedTime: int32(endTime.Sub(startTime).Seconds()), + }) + } + if err != nil { + return err + } + pointCount++ + for _, feature := range s.savedFeatures { + if proto.Equal(feature.Location, point) { + featureCount++ + } + } + if lastPoint != nil { + distance += calcDistance(lastPoint, point) + } + lastPoint = point + } +} +``` + +In the method body we use the `RouteGuide_RecordRouteServer`s `Recv()` method to repeatedly read +in our client's requests to a request object (in this case a `Point`) until there are no more +messages: the server needs to check the error returned from `Recv()` after each call. If this is +`nil`, the stream is still good and it can continue reading; if it's `io.EOF` the message stream +has ended and the server can return its `RouteSummary`. If it has any other value, we return the +error "as is" so that it'll be translated to an RPC status by the gRPC layer. + +#### Bidirectional streaming RPC +Finally, let's look at our bidirectional streaming RPC `RouteChat()`. + +```go +func (s *routeGuideServer) RouteChat(stream pb.RouteGuide_RouteChatServer) error { + for { + in, err := stream.Recv() + if err == io.EOF { + return nil + } + if err != nil { + return err + } + key := serialize(in.Location) + ... // look for notes to be sent to client + for _, note := range s.routeNotes[key] { + if err := stream.Send(note); err != nil { + return err + } + } + } +} +``` + +This time we get a `RouteGuide_RouteChatServer` stream that, as in our client-side streaming +example, can be used to read and write messages. However, this time we return values via our +method's stream while the client is still writing messages to *their* message stream. + +The syntax for reading and writing here is very similar to our client-streaming method, except +the server uses the stream's `Send()` method rather than `SendAndClose()` because it's writing +multiple responses. Although each side will always get the other's messages in the order they +were written, both the client and server can read and write in any order — the streams operate +completely independently. + +### Starting the server + +Once we've implemented all our methods, we also need to start up a gRPC server so that clients can +actually use our service. The following snippet shows how we do this for our `RouteGuide` service: + +```go +flag.Parse() +lis, err := net.Listen("tcp", fmt.Sprintf("localhost:%d", *port)) +if err != nil { + log.Fatalf("failed to listen: %v", err) +} +grpcServer := grpc.NewServer() +pb.RegisterRouteGuideServer(grpcServer, &routeGuideServer{}) +... // determine whether to use TLS +grpcServer.Serve(lis) +``` +To build and start a server, we: + +1. Specify the port we want to use to listen for client requests using `lis, err := net.Listen("tcp", + fmt.Sprintf("localhost:%d", *port))`. +2. Create an instance of the gRPC server using `grpc.NewServer()`. +3. Register our service implementation with the gRPC server. +4. Call `Serve()` on the server with our port details to do a blocking wait until the process is + killed or `Stop()` is called. + + +## Creating the client + +In this section, we'll look at creating a Go client for our `RouteGuide` service. You can see our +complete example client code in [grpc-go/examples/route_guide/client/client.go](https://github.com/grpc/grpc-go/tree/master/examples/route_guide/client/client.go). + +### Creating a stub + +To call service methods, we first need to create a gRPC *channel* to communicate with the server. +We create this by passing the server address and port number to `grpc.Dial()` as follows: + +```go +conn, err := grpc.Dial(*serverAddr) +if err != nil { + ... +} +defer conn.Close() +``` + +You can use `DialOptions` to set the auth credentials (e.g., TLS, GCE credentials, JWT credentials) +in `grpc.Dial` if the service you request requires that - however, we don't need to do this for our +`RouteGuide` service. + +Once the gRPC *channel* is setup, we need a client *stub* to perform RPCs. We get this using +the `NewRouteGuideClient` method provided in the `pb` package we generated from our `.proto` file. + +```go +client := pb.NewRouteGuideClient(conn) +``` + +### Calling service methods + +Now let's look at how we call our service methods. Note that in gRPC-Go, RPCs operate in a +blocking/synchronous mode, which means that the RPC call waits for the server to respond, and will +either return a response or an error. + +#### Simple RPC + +Calling the simple RPC `GetFeature` is nearly as straightforward as calling a local method. + +```go +feature, err := client.GetFeature(ctx, &pb.Point{409146138, -746188906}) +if err != nil { + ... +} +``` + +As you can see, we call the method on the stub we got earlier. In our method parameters we create +and populate a request protocol buffer object (in our case `Point`). We also pass a `context.Context` +object which lets us change our RPC's behaviour if necessary, such as time-out/cancel an RPC in +flight. If the call doesn't return an error, then we can read the response information from the +server from the first return value. + +```go +log.Println(feature) +``` + +#### Server-side streaming RPC + +Here's where we call the server-side streaming method `ListFeatures`, which returns a stream of +geographical `Feature`s. If you've already read [Creating the server](#server) some of this may look +very familiar - streaming RPCs are implemented in a similar way on both sides. + +```go +rect := &pb.Rectangle{ ... } // initialize a pb.Rectangle +stream, err := client.ListFeatures(ctx, rect) +if err != nil { + ... +} +for { + feature, err := stream.Recv() + if err == io.EOF { + break + } + if err != nil { + log.Fatalf("%v.ListFeatures(_) = _, %v", client, err) + } + log.Println(feature) +} +``` + +As in the simple RPC, we pass the method a context and a request. However, instead of getting a +response object back, we get back an instance of `RouteGuide_ListFeaturesClient`. The client can +use the `RouteGuide_ListFeaturesClient` stream to read the server's responses. + +We use the `RouteGuide_ListFeaturesClient`'s `Recv()` method to repeatedly read in the server's +responses to a response protocol buffer object (in this case a `Feature`) until there are no more +messages: the client needs to check the error `err` returned from `Recv()` after each call. +If `nil`, the stream is still good and it can continue reading; if it's `io.EOF` then the message +stream has ended; otherwise there must be an RPC error, which is passed over through `err`. + +#### Client-side streaming RPC + +The client-side streaming method `RecordRoute` is similar to the server-side method, except that +we only pass the method a context and get a `RouteGuide_RecordRouteClient` stream back, which we +can use to both write *and* read messages. + +```go +// Create a random number of random points +r := rand.New(rand.NewSource(time.Now().UnixNano())) +pointCount := int(r.Int31n(100)) + 2 // Traverse at least two points +var points []*pb.Point +for i := 0; i < pointCount; i++ { + points = append(points, randomPoint(r)) +} +log.Printf("Traversing %d points.", len(points)) +stream, err := client.RecordRoute(ctx) +if err != nil { + log.Fatalf("%v.RecordRoute(_) = _, %v", client, err) +} +for _, point := range points { + if err := stream.Send(point); err != nil { + log.Fatalf("%v.Send(%v) = %v", stream, point, err) + } +} +reply, err := stream.CloseAndRecv() +if err != nil { + log.Fatalf("%v.CloseAndRecv() got error %v, want %v", stream, err, nil) +} +log.Printf("Route summary: %v", reply) +``` + +The `RouteGuide_RecordRouteClient` has a `Send()` method that we can use to send requests to the +server. Once we've finished writing our client's requests to the stream using `Send()`, we need +to call `CloseAndRecv()` on the stream to let gRPC know that we've finished writing and are +expecting to receive a response. We get our RPC status from the `err` returned from `CloseAndRecv()`. +If the status is `nil`, then the first return value from `CloseAndRecv()` will be a valid server +response. + +#### Bidirectional streaming RPC + +Finally, let's look at our bidirectional streaming RPC `RouteChat()`. As in the case of `RecordRoute`, +we only pass the method a context object and get back a stream that we can use to both write and +read messages. However, this time we return values via our method's stream while the server is +still writing messages to *their* message stream. + +```go +stream, err := client.RouteChat(ctx) +waitc := make(chan struct{}) +go func() { + for { + in, err := stream.Recv() + if err == io.EOF { + // read done. + close(waitc) + return + } + if err != nil { + log.Fatalf("Failed to receive a note : %v", err) + } + log.Printf("Got message %s at point(%d, %d)", in.Message, in.Location.Latitude, in.Location.Longitude) + } +}() +for _, note := range notes { + if err := stream.Send(note); err != nil { + log.Fatalf("Failed to send a note: %v", err) + } +} +stream.CloseSend() +<-waitc +``` + +The syntax for reading and writing here is very similar to our client-side streaming method, except +we use the stream's `CloseSend()` method once we've finished our call. Although each side will always +get the other's messages in the order they were written, both the client and server can read and +write in any order — the streams operate completely independently. + +## Try it out! + +To compile and run the server, assuming you are in the folder +`$GOPATH/src/google.golang.org/grpc/examples/route_guide`, simply: + +```sh +$ go run server/server.go +``` + +Likewise, to run the client: + +```sh +$ go run client/client.go +``` + + +# TODO +## Code generation configuration +## Well known Types From 2459ff6c13dd5641907c88a8633048ca46eb8174 Mon Sep 17 00:00:00 2001 From: Juan Alvarez Date: Sun, 29 Sep 2019 23:37:34 -0500 Subject: [PATCH 02/29] remove original go code --- tonic-examples/routeguide-tutorial.md | 317 +------------------------- 1 file changed, 3 insertions(+), 314 deletions(-) diff --git a/tonic-examples/routeguide-tutorial.md b/tonic-examples/routeguide-tutorial.md index 28796d547..df2a3b757 100644 --- a/tonic-examples/routeguide-tutorial.md +++ b/tonic-examples/routeguide-tutorial.md @@ -293,345 +293,34 @@ impl server::RouteGuide for RouteGuide { [async-trait]: https://github.com/dtolnay/async-trait #### Simple RPC -`routeGuideServer` implements all our service methods. Let's look at the simplest type -first, `GetFeature`, which just gets a `Point` from the client and returns the corresponding -feature information from its database in a `Feature`. - -```go -func (s *routeGuideServer) GetFeature(ctx context.Context, point *pb.Point) (*pb.Feature, error) { - for _, feature := range s.savedFeatures { - if proto.Equal(feature.Location, point) { - return feature, nil - } - } - // No feature was found, return an unnamed feature - return &pb.Feature{"", point}, nil -} -``` -The method is passed a context object for the RPC and the client's `Point` protocol buffer request. -It returns a `Feature` protocol buffer object with the response information and an `error`. In the -method we populate the `Feature` with the appropriate information, and then `return` it along with -an `nil` error to tell gRPC that we've finished dealing with the RPC and that the `Feature` can be -returned to the client. #### Server-side streaming RPC -Now let's look at one of our streaming RPCs. `ListFeatures` is a server-side streaming RPC, so we -need to send back multiple `Feature`s to our client. - -```go -func (s *routeGuideServer) ListFeatures(rect *pb.Rectangle, stream pb.RouteGuide_ListFeaturesServer) error { - for _, feature := range s.savedFeatures { - if inRange(feature.Location, rect) { - if err := stream.Send(feature); err != nil { - return err - } - } - } - return nil -} -``` - -As you can see, instead of getting simple request and response objects in our method parameters, -this time we get a request object (the `Rectangle` in which our client wants to find `Feature`s) -and a special `RouteGuide_ListFeaturesServer` object to write our responses. - -In the method, we populate as many `Feature` objects as we need to return, writing them to the -`RouteGuide_ListFeaturesServer` using its `Send()` method. Finally, as in our simple RPC, we return -a `nil` error to tell gRPC that we've finished writing responses. Should any error happen in this -call, we return a non-`nil` error; the gRPC layer will translate it into an appropriate RPC status -to be sent on the wire. #### Client-side streaming RPC -Now let's look at something a little more complicated: the client-side streaming method -`RecordRoute`, where we get a stream of `Point`s from the client and return a single -`RouteSummary` with information about their trip. As you can see, this time the method doesn't -have a request parameter at all. Instead, it gets a `RouteGuide_RecordRouteServer` stream, which -the server can use to both read *and* write messages - it can receive client messages using its -`Recv()` method and return its single response using its `SendAndClose()` method. - -```go -func (s *routeGuideServer) RecordRoute(stream pb.RouteGuide_RecordRouteServer) error { - var pointCount, featureCount, distance int32 - var lastPoint *pb.Point - startTime := time.Now() - for { - point, err := stream.Recv() - if err == io.EOF { - endTime := time.Now() - return stream.SendAndClose(&pb.RouteSummary{ - PointCount: pointCount, - FeatureCount: featureCount, - Distance: distance, - ElapsedTime: int32(endTime.Sub(startTime).Seconds()), - }) - } - if err != nil { - return err - } - pointCount++ - for _, feature := range s.savedFeatures { - if proto.Equal(feature.Location, point) { - featureCount++ - } - } - if lastPoint != nil { - distance += calcDistance(lastPoint, point) - } - lastPoint = point - } -} -``` - -In the method body we use the `RouteGuide_RecordRouteServer`s `Recv()` method to repeatedly read -in our client's requests to a request object (in this case a `Point`) until there are no more -messages: the server needs to check the error returned from `Recv()` after each call. If this is -`nil`, the stream is still good and it can continue reading; if it's `io.EOF` the message stream -has ended and the server can return its `RouteSummary`. If it has any other value, we return the -error "as is" so that it'll be translated to an RPC status by the gRPC layer. #### Bidirectional streaming RPC -Finally, let's look at our bidirectional streaming RPC `RouteChat()`. - -```go -func (s *routeGuideServer) RouteChat(stream pb.RouteGuide_RouteChatServer) error { - for { - in, err := stream.Recv() - if err == io.EOF { - return nil - } - if err != nil { - return err - } - key := serialize(in.Location) - ... // look for notes to be sent to client - for _, note := range s.routeNotes[key] { - if err := stream.Send(note); err != nil { - return err - } - } - } -} -``` - -This time we get a `RouteGuide_RouteChatServer` stream that, as in our client-side streaming -example, can be used to read and write messages. However, this time we return values via our -method's stream while the client is still writing messages to *their* message stream. - -The syntax for reading and writing here is very similar to our client-streaming method, except -the server uses the stream's `Send()` method rather than `SendAndClose()` because it's writing -multiple responses. Although each side will always get the other's messages in the order they -were written, both the client and server can read and write in any order — the streams operate -completely independently. ### Starting the server -Once we've implemented all our methods, we also need to start up a gRPC server so that clients can -actually use our service. The following snippet shows how we do this for our `RouteGuide` service: - -```go -flag.Parse() -lis, err := net.Listen("tcp", fmt.Sprintf("localhost:%d", *port)) -if err != nil { - log.Fatalf("failed to listen: %v", err) -} -grpcServer := grpc.NewServer() -pb.RegisterRouteGuideServer(grpcServer, &routeGuideServer{}) -... // determine whether to use TLS -grpcServer.Serve(lis) -``` -To build and start a server, we: - -1. Specify the port we want to use to listen for client requests using `lis, err := net.Listen("tcp", - fmt.Sprintf("localhost:%d", *port))`. -2. Create an instance of the gRPC server using `grpc.NewServer()`. -3. Register our service implementation with the gRPC server. -4. Call `Serve()` on the server with our port details to do a blocking wait until the process is - killed or `Stop()` is called. ## Creating the client -In this section, we'll look at creating a Go client for our `RouteGuide` service. You can see our -complete example client code in [grpc-go/examples/route_guide/client/client.go](https://github.com/grpc/grpc-go/tree/master/examples/route_guide/client/client.go). - ### Creating a stub -To call service methods, we first need to create a gRPC *channel* to communicate with the server. -We create this by passing the server address and port number to `grpc.Dial()` as follows: - -```go -conn, err := grpc.Dial(*serverAddr) -if err != nil { - ... -} -defer conn.Close() -``` - -You can use `DialOptions` to set the auth credentials (e.g., TLS, GCE credentials, JWT credentials) -in `grpc.Dial` if the service you request requires that - however, we don't need to do this for our -`RouteGuide` service. - -Once the gRPC *channel* is setup, we need a client *stub* to perform RPCs. We get this using -the `NewRouteGuideClient` method provided in the `pb` package we generated from our `.proto` file. - -```go -client := pb.NewRouteGuideClient(conn) -``` - ### Calling service methods -Now let's look at how we call our service methods. Note that in gRPC-Go, RPCs operate in a -blocking/synchronous mode, which means that the RPC call waits for the server to respond, and will -either return a response or an error. - #### Simple RPC -Calling the simple RPC `GetFeature` is nearly as straightforward as calling a local method. - -```go -feature, err := client.GetFeature(ctx, &pb.Point{409146138, -746188906}) -if err != nil { - ... -} -``` - -As you can see, we call the method on the stub we got earlier. In our method parameters we create -and populate a request protocol buffer object (in our case `Point`). We also pass a `context.Context` -object which lets us change our RPC's behaviour if necessary, such as time-out/cancel an RPC in -flight. If the call doesn't return an error, then we can read the response information from the -server from the first return value. - -```go -log.Println(feature) -``` - #### Server-side streaming RPC -Here's where we call the server-side streaming method `ListFeatures`, which returns a stream of -geographical `Feature`s. If you've already read [Creating the server](#server) some of this may look -very familiar - streaming RPCs are implemented in a similar way on both sides. - -```go -rect := &pb.Rectangle{ ... } // initialize a pb.Rectangle -stream, err := client.ListFeatures(ctx, rect) -if err != nil { - ... -} -for { - feature, err := stream.Recv() - if err == io.EOF { - break - } - if err != nil { - log.Fatalf("%v.ListFeatures(_) = _, %v", client, err) - } - log.Println(feature) -} -``` - -As in the simple RPC, we pass the method a context and a request. However, instead of getting a -response object back, we get back an instance of `RouteGuide_ListFeaturesClient`. The client can -use the `RouteGuide_ListFeaturesClient` stream to read the server's responses. - -We use the `RouteGuide_ListFeaturesClient`'s `Recv()` method to repeatedly read in the server's -responses to a response protocol buffer object (in this case a `Feature`) until there are no more -messages: the client needs to check the error `err` returned from `Recv()` after each call. -If `nil`, the stream is still good and it can continue reading; if it's `io.EOF` then the message -stream has ended; otherwise there must be an RPC error, which is passed over through `err`. - #### Client-side streaming RPC -The client-side streaming method `RecordRoute` is similar to the server-side method, except that -we only pass the method a context and get a `RouteGuide_RecordRouteClient` stream back, which we -can use to both write *and* read messages. - -```go -// Create a random number of random points -r := rand.New(rand.NewSource(time.Now().UnixNano())) -pointCount := int(r.Int31n(100)) + 2 // Traverse at least two points -var points []*pb.Point -for i := 0; i < pointCount; i++ { - points = append(points, randomPoint(r)) -} -log.Printf("Traversing %d points.", len(points)) -stream, err := client.RecordRoute(ctx) -if err != nil { - log.Fatalf("%v.RecordRoute(_) = _, %v", client, err) -} -for _, point := range points { - if err := stream.Send(point); err != nil { - log.Fatalf("%v.Send(%v) = %v", stream, point, err) - } -} -reply, err := stream.CloseAndRecv() -if err != nil { - log.Fatalf("%v.CloseAndRecv() got error %v, want %v", stream, err, nil) -} -log.Printf("Route summary: %v", reply) -``` - -The `RouteGuide_RecordRouteClient` has a `Send()` method that we can use to send requests to the -server. Once we've finished writing our client's requests to the stream using `Send()`, we need -to call `CloseAndRecv()` on the stream to let gRPC know that we've finished writing and are -expecting to receive a response. We get our RPC status from the `err` returned from `CloseAndRecv()`. -If the status is `nil`, then the first return value from `CloseAndRecv()` will be a valid server -response. - #### Bidirectional streaming RPC -Finally, let's look at our bidirectional streaming RPC `RouteChat()`. As in the case of `RecordRoute`, -we only pass the method a context object and get back a stream that we can use to both write and -read messages. However, this time we return values via our method's stream while the server is -still writing messages to *their* message stream. - -```go -stream, err := client.RouteChat(ctx) -waitc := make(chan struct{}) -go func() { - for { - in, err := stream.Recv() - if err == io.EOF { - // read done. - close(waitc) - return - } - if err != nil { - log.Fatalf("Failed to receive a note : %v", err) - } - log.Printf("Got message %s at point(%d, %d)", in.Message, in.Location.Latitude, in.Location.Longitude) - } -}() -for _, note := range notes { - if err := stream.Send(note); err != nil { - log.Fatalf("Failed to send a note: %v", err) - } -} -stream.CloseSend() -<-waitc -``` - -The syntax for reading and writing here is very similar to our client-side streaming method, except -we use the stream's `CloseSend()` method once we've finished our call. Although each side will always -get the other's messages in the order they were written, both the client and server can read and -write in any order — the streams operate completely independently. - ## Try it out! -To compile and run the server, assuming you are in the folder -`$GOPATH/src/google.golang.org/grpc/examples/route_guide`, simply: - -```sh -$ go run server/server.go -``` - -Likewise, to run the client: - -```sh -$ go run client/client.go -``` - - -# TODO -## Code generation configuration -## Well known Types +## Appendix +### tonic-build configuration +### Well known Types From 4d7f1431c6a311febeabdc6305dcb640a55b71bf Mon Sep 17 00:00:00 2001 From: Juan Alvarez Date: Tue, 1 Oct 2019 08:10:59 -0500 Subject: [PATCH 03/29] Apply suggestions from code review Co-Authored-By: Lucio Franco --- tonic-examples/routeguide-tutorial.md | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/tonic-examples/routeguide-tutorial.md b/tonic-examples/routeguide-tutorial.md index df2a3b757..e23de26a4 100644 --- a/tonic-examples/routeguide-tutorial.md +++ b/tonic-examples/routeguide-tutorial.md @@ -39,7 +39,7 @@ To run the sample code and walk through the tutorial, the only prerequisite is R Clone or download tonic's repository: ```shell -git clone https://github.com/LucioFranco/tonic.git +git clone https://github.com/hyperium/tonic.git ``` Change your current directory to tonic's repository root: @@ -90,7 +90,7 @@ $ mkdir proto && touch proto/route_guide.proto You can see the complete `.proto` file in [tonic-examples/proto/routeguide/route_guide.proto][routeguide-proto]. -[routeguide-proto]: https://github.com/LucioFranco/tonic/blob/master/tonic-examples/proto/routeguide/route_guide.proto +[routeguide-proto]: https://github.com/hyperium/tonic/blob/master/tonic-examples/proto/routeguide/route_guide.proto To define a service, you specify a named `service` in your `.proto` file: @@ -172,16 +172,16 @@ Edit `Cargo.toml` to add all the dependencies we'll need for this example: ```toml [dependencies] -tonic = { path = "../tonic/tonic" } # TODO: update once there is a released version -futures-preview = { version = "=0.3.0-alpha.18", default-features = false, features = ["alloc"]} -tokio = "=0.2.0-alpha.4" +tonic = "0.1.0-alpha.1" +futures-preview = { version = "0.3.0-alpha.19", default-features = false, features = ["alloc"]} +tokio = "0.2.0-alpha.6" prost = "0.5" bytes = "0.4" serde_json = "1.0" serde = { version = "1.0", features = ["derive"] } [build-dependencies] -tonic-build = { path = "../tonic/tonic-build" } # TODO: update to released version +tonic-build = "0.1.0-alpha.1" ``` Create a `build.rs` file at the root of your crate: From 87d5ef3f25aa96b26211e4fedd5015e252dc6eb3 Mon Sep 17 00:00:00 2001 From: Juan Alvarez Date: Tue, 1 Oct 2019 10:24:33 -0500 Subject: [PATCH 04/29] update urls, make capitalization consistent --- tonic-examples/routeguide-tutorial.md | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/tonic-examples/routeguide-tutorial.md b/tonic-examples/routeguide-tutorial.md index e23de26a4..77f7cf4b3 100644 --- a/tonic-examples/routeguide-tutorial.md +++ b/tonic-examples/routeguide-tutorial.md @@ -36,18 +36,18 @@ To run the sample code and walk through the tutorial, the only prerequisite is R ## Running the example -Clone or download tonic's repository: +Clone or download Tonic's repository: ```shell git clone https://github.com/hyperium/tonic.git ``` -Change your current directory to tonic's repository root: +Change your current directory to Tonic's repository root: ```shell $ cd tonic ``` -Tonic uses rustfmt to tidy up the code it generates, make sure it's installed. +Tonic uses `rustfmt` to tidy up the code it generates, make sure it's installed. ```shell $ rustup component add rustfmt @@ -63,7 +63,7 @@ In a separate shell, run the client $ cargo run --bin routeguide-client ``` -**Note:** Prior to rust's 1.39 release, tonic may be pinned to a specific toolchain version. +**Note:** Prior to rust's 1.39 release, Tonic may be pinned to a specific toolchain version. ## Project setup @@ -104,8 +104,8 @@ Then you define `rpc` methods inside your service definition, specifying their r types. gRPC lets you define four kinds of service method, all of which are used in the `RouteGuide` service: -- A *simple RPC* where the client sends a request to the server using the stub and waits for a -response to come back, just like a normal function call. +- A *simple RPC* where the client sends a request to the server and waits for a response to come +back, just like a normal function call. ```proto // Obtains the feature at a given position. rpc GetFeature(Point) returns (Feature) {} @@ -124,17 +124,17 @@ placing the `stream` keyword before the *response* type. ``` - A *client-side streaming RPC* where the client writes a sequence of messages and sends them to -the server, again using a provided stream. Once the client has finished writing the messages, -it waits for the server to read them all and return its response. You specify a client-side -streaming method by placing the `stream` keyword before the *request* type. +the server. Once the client has finished writing the messages, it waits for the server to read them +all and return its response. You specify a client-side streaming method by placing the `stream` +keyword before the *request* type. ```proto // Accepts a stream of Points on a route being traversed, returning a // RouteSummary when traversal is completed. rpc RecordRoute(stream Point) returns (RouteSummary) {} ``` -- A *bidirectional streaming RPC* where both sides send a sequence of messages using a read-write -stream. The two streams operate independently, so clients and servers can read and write in whatever +- A *bidirectional streaming RPC* where both sides send a sequence of messages. The two streams +operate independently, so clients and servers can read and write in whatever order they like: for example, the server could wait to receive all the client messages before writing its responses, or it could alternately read a message then write a message, or some other combination of reads and writes. The order of messages in each stream is preserved. You specify @@ -221,7 +221,7 @@ There are two parts to making our `RouteGuide` service do its job: You can find our example `RouteGuide` server in [tonic-examples/src/routeguide/server.rs][routeguide-server] -[routeguide-server]: https://github.com/LucioFranco/tonic/blob/master/tonic-examples/src/routeguide/server.rs +[routeguide-server]: https://github.com/hyperium/tonic/blob/master/tonic-examples/src/routeguide/server.rs ### Implementing the server::RouteGuide trait From d11d7b62db28aec748022a873f2f56b686de4138 Mon Sep 17 00:00:00 2001 From: Juan Alvarez Date: Tue, 1 Oct 2019 14:33:18 -0500 Subject: [PATCH 05/29] pin all alpha dependencies --- tonic-examples/Cargo.toml | 2 +- tonic-interop/Cargo.toml | 4 ++-- tonic/Cargo.toml | 6 +++--- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/tonic-examples/Cargo.toml b/tonic-examples/Cargo.toml index 24f08ae04..78c34c306 100644 --- a/tonic-examples/Cargo.toml +++ b/tonic-examples/Cargo.toml @@ -54,7 +54,7 @@ tokio = "=0.2.0-alpha.6" futures-preview = { version = "=0.3.0-alpha.19", default-features = false, features = ["alloc"]} async-stream = "0.1.2" http = "0.1" -tower = "0.3.0-alpha.2" +tower = "=0.3.0-alpha.2" # Required for routeguide serde = { version = "1.0", features = ["derive"] } diff --git a/tonic-interop/Cargo.toml b/tonic-interop/Cargo.toml index 410f3685e..5ede3a343 100644 --- a/tonic-interop/Cargo.toml +++ b/tonic-interop/Cargo.toml @@ -22,8 +22,8 @@ http = "0.1" futures-core-preview = "=0.3.0-alpha.19" futures-util-preview = "=0.3.0-alpha.19" async-stream = "0.1.2" -tower = "0.3.0-alpha.2" -http-body = "0.2.0-alpha.2" +tower = "=0.3.0-alpha.2" +http-body = "=0.2.0-alpha.2" console = "0.7" structopt = "0.2" diff --git a/tonic/Cargo.toml b/tonic/Cargo.toml index 117b53ec6..299ec4257 100644 --- a/tonic/Cargo.toml +++ b/tonic/Cargo.toml @@ -49,7 +49,7 @@ percent-encoding = "1.0.1" tower-service = "=0.3.0-alpha.2" tokio-codec = "=0.2.0-alpha.6" async-stream = "0.1.2" -http-body = "0.2.0-alpha.3" +http-body = "=0.2.0-alpha.2" pin-project = "^0.4" # prost @@ -60,7 +60,7 @@ prost-derive = { version = "0.5", optional = true } async-trait = { version = "0.1.13", optional = true } # transport -hyper = { version = "0.13.0-alpha.3", features = ["unstable-stream"], optional = true } +hyper = { version = "=0.13.0-alpha.3", features = ["unstable-stream"], optional = true } tokio = { version = "=0.2.0-alpha.6", default-features = false, features = ["tcp"], optional = true } tower = { version = "=0.3.0-alpha.2", optional = true} tower-make = "=0.3.0-alpha.2a" @@ -73,4 +73,4 @@ tokio-openssl = { version = "=0.4.0-alpha.6", optional = true } openssl1 = { package = "openssl", version = "0.10", optional = true } # rustls -tokio-rustls = { version = "0.12.0-alpha.4", optional = true } +tokio-rustls = { version = "=0.12.0-alpha.4", optional = true } From 2883d802f3efb35e9c90ee4cd55bf1cd8f955726 Mon Sep 17 00:00:00 2001 From: Juan Alvarez Date: Tue, 1 Oct 2019 17:04:50 -0500 Subject: [PATCH 06/29] some method implementations --- tonic-examples/routeguide-tutorial.md | 233 +++++++++++++++++++++++++- 1 file changed, 228 insertions(+), 5 deletions(-) diff --git a/tonic-examples/routeguide-tutorial.md b/tonic-examples/routeguide-tutorial.md index 77f7cf4b3..6f8d0d2ae 100644 --- a/tonic-examples/routeguide-tutorial.md +++ b/tonic-examples/routeguide-tutorial.md @@ -178,7 +178,7 @@ tokio = "0.2.0-alpha.6" prost = "0.5" bytes = "0.4" serde_json = "1.0" -serde = { version = "1.0", features = ["derive"] } +#serde = { version = "1.0", features = ["derive"] } [build-dependencies] tonic-build = "0.1.0-alpha.1" @@ -237,13 +237,13 @@ The generated code is placed inside our target directory, in a location defined environment variable that is set by cargo. For our example, this means you can find the generated code in a path similar to `target/debug/build/routeguide/out/routeguide.rs`. -You can read more about the `OUT_DIR` variable in the [cargo book][cargo-book]. +You can learn more about `build.rs` the `OUT_DIR` environment variable in the [cargo book][cargo-book]. We can bring this code into scope like this: ```rust pub mod routeguide { - include!(concat!(env!("OUT_DIR"), "/routeguide.rs")); + tonic::include_proto!("routeguide"); } use routeguide::{server, Feature, Point, Rectangle, RouteNote, RouteSummary}; @@ -292,33 +292,256 @@ impl server::RouteGuide for RouteGuide { [async-trait]: https://github.com/dtolnay/async-trait +### Adding state +There are two pieces of state our service needs to access: an immutable list of features and a +a mutable map from points to route notes. + +When thinking about state, it is important to consider that: + +- Tonic will run our server in a multi-threaded Tokio executor +- The `server::RouteGuide` trait has `Send + Sync + 'static` bounds + +This in one way to represent our state: + +```rust +#[derive(Debug)] +pub struct RouteGuide { + state: State, +} + +#[derive(Debug, Clone)] +struct State { + features: Arc>, + notes: Arc>>>, +} +``` + +We also need to implement `Hash` and `Eq` for `Point` so we can use `point` values as map keys. + +```rust +impl Hash for Point { + fn hash(&self, state: &mut H) + where + H: Hasher, + { + self.latitude.hash(state); + self.longitude.hash(state); + } +} + +impl Eq for Point {} + +``` + + #### Simple RPC +Let's look at the simplest method first, `get_feature`, which just gets a `Point` from the client +and tries to find a feature associated with that location. If it finds one, it is returned to the +client wrapped in a `tonic::Response`. If it can't find one, it returns an empty Feature. +```rust +async fn get_feature(&self, request: Request) -> Result, Status> { + for feature in &self.state.features[..] { + if feature.location.as_ref() == Some(request.get_ref()) { + return Ok(Response::new(feature.clone())); + } + } + + let response = Response::new(Feature { + name: "".to_string(), + location: None, + }); + + Ok(response) +} +``` + +The method is passed a `tonic::Request` that contains the client's `Point` protocol buffer. It +returns a `Result` with a `Feature` protocol buffer wrapped in a `tonic::Response` or a +tonic::Status, representing an error. #### Server-side streaming RPC +Now let's look at one of our streaming RPCs. `list_features` is a server-side streaming RPC, so we +need to send back multiple `Feature`s to our client. + +```rust + type ListFeaturesStream = mpsc::Receiver>; + + async fn list_features( + &self, + request: Request, + ) -> Result, Status> { + let (mut tx, rx) = mpsc::channel(4); + + let state = self.state.clone(); + + tokio::spawn(async move { + for feature in &state.features[..] { + if in_range(feature.location.as_ref().unwrap(), request.get_ref()) { + println!(" => send {:?}", feature); + tx.send(Ok(feature.clone())).await.unwrap(); + } + } + }); + + Ok(Response::new(rx)) + } +``` + +Similar to the `get_feature` method, `list_features` `tonic::Request` where T is in this case a +`Rectangle`. This time, however, we need to return a stream of values, rather than a single one. + +TODO: finish description #### Client-side streaming RPC +Now let's look at something a little more complicated: the client-side streaming method +`record_route`, where we get a stream of `Point`s from the client and return a single `RouteSummary` +with information about their trip. As you can see, this time the method receives a +`tonic::Request>` + +```rust +async fn record_route( + &self, + request: Request>, +) -> Result, Status> { + let stream = request.into_inner(); + futures::pin_mut!(stream); + + let mut summary = RouteSummary::default(); + let mut last_point = None; + let now = Instant::now(); + + while let Some(point) = stream.next().await { + let point = point?; + summary.point_count += 1; + + for feature in &self.state.features[..] { + if feature.location.as_ref() == Some(&point) { + summary.feature_count += 1; + } + } + + if let Some(ref last_point) = last_point { + summary.distance += calc_distance(last_point, &point); + } + + last_point = Some(point); + } + + summary.elapsed_time = now.elapsed().as_secs() as i32; + + Ok(Response::new(summary)) +} +``` #### Bidirectional streaming RPC +Finally, let's look at our bidirectional streaming RPC `route_chat`. +```rust +async fn route_chat( + &self, + request: Request>, +) -> Result, Status> { + println!("RouteChat"); + + let stream = request.into_inner(); + let mut state = self.state.clone(); + + let output = async_stream::try_stream! { + futures::pin_mut!(stream); + + while let Some(note) = stream.next().await { + let note = note?; + + let location = note.location.clone().unwrap(); + + let mut notes = state.notes.lock().await; + let notes = notes.entry(location).or_insert(vec![]); + notes.push(note); + + for note in notes { + yield note.clone(); + } + } + }; + + Ok(Response::new(Box::pin(output) + as Pin< + Box> + Send + 'static>, + >)) + } +} +``` ### Starting the server +Once we've implemented all our methods, we also need to start up a gRPC server so that clients can +actually use our service. The following snippet shows how we do this for our `RouteGuide` service: + +```rust +let addr = "[::1]:10000".parse().unwrap(); + +let route_guide = RouteGuide { + state: State { + features: Arc::new(data::load()), + notes: Lock::new(HashMap::new()), + }, +}; + +let svc = server::RouteGuideServer::new(route_guide); +Server::builder().serve(addr, svc).await?; +``` + +To build and start a server, we: + +1. Specify the socket address to use to listen for client requests using `let addr = "[::1]:10000".parse().unwrap();`. +2. Create an instance of the gRPC server `RouteGuide {...}`. +3. Register our service implementation with the gRPC server `RouteGuideServer::new(...)`. +4. Call `Server::builder().serve(...)` to do a blocking wait until the process is killed. + ## Creating the client -### Creating a stub +In this section, we'll look at creating a Rust client for our `RouteGuide` service. You can see our +complete example client code in TODO + +### Creating a client + +To call service methods, we first need to create a gRPC *client* to communicate with the server. +We create this by passing the server's URL to `RouteGuideClient::connect` as follows: ### Calling service methods +Now let's look at how we call our service methods. Note that in Tonic, RPCs are asynchronous, +which means that the RPC call needs to be awaited. #### Simple RPC +Calling the simple RPC `GetFeature` is nearly as straightforward as calling a local method. + +```rust +let response = client + .get_feature(Request::new(Point { + latitude: 409146138, + longitude: -746188906, + })) + .await?; +``` +As you can see, we call the method on the client we got earlier. In our method parameters we create +and populate a request protocol buffer object (in our case `Point`). If the call doesn't return an +error, then we can read the response information from the server from the first return value. #### Server-side streaming RPC -#### Client-side streaming RPC +Here's where we call the server-side streaming method `list_features`, which returns a stream of +geographical `Feature`s. + +```rust +``` #### Bidirectional streaming RPC +```rust +``` + ## Try it out! ## Appendix From ea649a67a5cdb48cd0dda59e0cce1055bbdc69ca Mon Sep 17 00:00:00 2001 From: Juan Alvarez Date: Tue, 1 Oct 2019 17:19:21 -0500 Subject: [PATCH 07/29] spurious commit, testing CI --- tonic-examples/routeguide-tutorial.md | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/tonic-examples/routeguide-tutorial.md b/tonic-examples/routeguide-tutorial.md index 6f8d0d2ae..b02df9a25 100644 --- a/tonic-examples/routeguide-tutorial.md +++ b/tonic-examples/routeguide-tutorial.md @@ -268,7 +268,7 @@ impl server::RouteGuide for RouteGuide { ) -> Result, Status> { unimplemented!() } - + async fn record_route( &self, _request: Request>, @@ -544,6 +544,10 @@ geographical `Feature`s. ## Try it out! +### Run the server + +### Run the client + ## Appendix ### tonic-build configuration ### Well known Types From e3ea43cc9e6f520e5ad3f7e239f739871a0c907d Mon Sep 17 00:00:00 2001 From: Juan Alvarez Date: Tue, 1 Oct 2019 17:21:44 -0500 Subject: [PATCH 08/29] spurious 2 --- tonic-examples/routeguide-tutorial.md | 3 --- 1 file changed, 3 deletions(-) diff --git a/tonic-examples/routeguide-tutorial.md b/tonic-examples/routeguide-tutorial.md index b02df9a25..b8fe59f58 100644 --- a/tonic-examples/routeguide-tutorial.md +++ b/tonic-examples/routeguide-tutorial.md @@ -531,9 +531,6 @@ error, then we can read the response information from the server from the first #### Server-side streaming RPC -Here's where we call the server-side streaming method `list_features`, which returns a stream of -geographical `Feature`s. - ```rust ``` From d6d2efd4a5fa5cc4b27a527fa1b275e80d4b44cc Mon Sep 17 00:00:00 2001 From: Juan Alvarez Date: Tue, 1 Oct 2019 21:11:49 -0500 Subject: [PATCH 09/29] server state --- tonic-examples/routeguide-tutorial.md | 36 +++++++++++++++++++++------ 1 file changed, 28 insertions(+), 8 deletions(-) diff --git a/tonic-examples/routeguide-tutorial.md b/tonic-examples/routeguide-tutorial.md index b8fe59f58..4362be391 100644 --- a/tonic-examples/routeguide-tutorial.md +++ b/tonic-examples/routeguide-tutorial.md @@ -292,16 +292,14 @@ impl server::RouteGuide for RouteGuide { [async-trait]: https://github.com/dtolnay/async-trait -### Adding state +### Server state There are two pieces of state our service needs to access: an immutable list of features and a -a mutable map from points to route notes. +mutable map from points to route notes. -When thinking about state, it is important to consider that: - -- Tonic will run our server in a multi-threaded Tokio executor -- The `server::RouteGuide` trait has `Send + Sync + 'static` bounds +When designing our state shape, we must consider that our server will run in a multi-threaded Tokio +executor and that the `server::RouteGuide` trait has `Send + Sync + 'static` bounds. -This in one way to represent our state: +This in one way we can represent our state: ```rust #[derive(Debug)] @@ -316,7 +314,29 @@ struct State { } ``` -We also need to implement `Hash` and `Eq` for `Point` so we can use `point` values as map keys. +When our server boots, we are going to deserialize our features vector from a json file. +Create the data file and some helper code to read and deserialize our features. + +```shell +$ mkdir data && touch data/route_guide_db.json +$ touch src/data.rs +``` + +You can find our example `RouteGuide` server in +[tonic-examples/src/routeguide/server.rs][routeguide-server] + + +You can find our example json data in [tonic-examples/data/route_guide_db.json][route-guide-db] and +the corresponding `data` module to load and deserialize it in +[tonic-examples/routeguide/data.rs][data-module] + +Lastly, we need to implement `Hash` and `Eq` for `Point` so we can use `point` values as map keys. + +[route-guide-db]: https://github.com/hyperium/tonic/blob/master/tonic-examples/data/route_guide_db.json +[data-module]: https://github.com/hyperium/tonic/blob/master/tonic-examples/src/routeguide/data.rs + +Lastly, we need to implement `Hash` and `Eq` for `Point` so we can use `point` values as map keys. + ```rust impl Hash for Point { From 14a4e1349c45ad1a9f4c19ae8bfde8f99a2ef352 Mon Sep 17 00:00:00 2001 From: Juan Alvarez Date: Tue, 1 Oct 2019 21:20:17 -0500 Subject: [PATCH 10/29] delete repeted lines, wording --- tonic-examples/routeguide-tutorial.md | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/tonic-examples/routeguide-tutorial.md b/tonic-examples/routeguide-tutorial.md index 4362be391..01bd1612f 100644 --- a/tonic-examples/routeguide-tutorial.md +++ b/tonic-examples/routeguide-tutorial.md @@ -178,7 +178,7 @@ tokio = "0.2.0-alpha.6" prost = "0.5" bytes = "0.4" serde_json = "1.0" -#serde = { version = "1.0", features = ["derive"] } +serde = { version = "1.0", features = ["derive"] } [build-dependencies] tonic-build = "0.1.0-alpha.1" @@ -315,17 +315,13 @@ struct State { ``` When our server boots, we are going to deserialize our features vector from a json file. -Create the data file and some helper code to read and deserialize our features. +Create the data file and a helper module to read and deserialize our features. ```shell $ mkdir data && touch data/route_guide_db.json $ touch src/data.rs ``` -You can find our example `RouteGuide` server in -[tonic-examples/src/routeguide/server.rs][routeguide-server] - - You can find our example json data in [tonic-examples/data/route_guide_db.json][route-guide-db] and the corresponding `data` module to load and deserialize it in [tonic-examples/routeguide/data.rs][data-module] @@ -335,8 +331,6 @@ Lastly, we need to implement `Hash` and `Eq` for `Point` so we can use `point` v [route-guide-db]: https://github.com/hyperium/tonic/blob/master/tonic-examples/data/route_guide_db.json [data-module]: https://github.com/hyperium/tonic/blob/master/tonic-examples/src/routeguide/data.rs -Lastly, we need to implement `Hash` and `Eq` for `Point` so we can use `point` values as map keys. - ```rust impl Hash for Point { From bbb856198311a3971bff12b66a8b94eb3df03b18 Mon Sep 17 00:00:00 2001 From: Juan Alvarez Date: Tue, 1 Oct 2019 21:52:17 -0500 Subject: [PATCH 11/29] improve route_chat, record_route and list_features --- tonic-examples/routeguide-tutorial.md | 73 ++++++++++++++------------- 1 file changed, 37 insertions(+), 36 deletions(-) diff --git a/tonic-examples/routeguide-tutorial.md b/tonic-examples/routeguide-tutorial.md index 01bd1612f..3925c0bb3 100644 --- a/tonic-examples/routeguide-tutorial.md +++ b/tonic-examples/routeguide-tutorial.md @@ -349,9 +349,9 @@ impl Eq for Point {} #### Simple RPC -Let's look at the simplest method first, `get_feature`, which just gets a `Point` from the client -and tries to find a feature associated with that location. If it finds one, it is returned to the -client wrapped in a `tonic::Response`. If it can't find one, it returns an empty Feature. +Let's look at the simplest method first, `get_feature`, which just gets a `tonic::Request` +from the client and tries to find a feature at the location represented by the given `Point`. +If no feature is found, it returns an empty one. ```rust async fn get_feature(&self, request: Request) -> Result, Status> { @@ -370,42 +370,37 @@ async fn get_feature(&self, request: Request) -> Result } ``` -The method is passed a `tonic::Request` that contains the client's `Point` protocol buffer. It -returns a `Result` with a `Feature` protocol buffer wrapped in a `tonic::Response` or a -tonic::Status, representing an error. #### Server-side streaming RPC Now let's look at one of our streaming RPCs. `list_features` is a server-side streaming RPC, so we need to send back multiple `Feature`s to our client. ```rust - type ListFeaturesStream = mpsc::Receiver>; +type ListFeaturesStream = mpsc::Receiver>; - async fn list_features( - &self, - request: Request, - ) -> Result, Status> { - let (mut tx, rx) = mpsc::channel(4); +async fn list_features( + &self, + request: Request, +) -> Result, Status> { + let (mut tx, rx) = mpsc::channel(4); - let state = self.state.clone(); + let state = self.state.clone(); - tokio::spawn(async move { - for feature in &state.features[..] { - if in_range(feature.location.as_ref().unwrap(), request.get_ref()) { - println!(" => send {:?}", feature); - tx.send(Ok(feature.clone())).await.unwrap(); - } + tokio::spawn(async move { + for feature in &state.features[..] { + if in_range(feature.location.as_ref().unwrap(), request.get_ref()) { + tx.send(Ok(feature.clone())).await.unwrap(); } - }); + } + }); - Ok(Response::new(rx)) - } + Ok(Response::new(rx)) +} ``` -Similar to the `get_feature` method, `list_features` `tonic::Request` where T is in this case a -`Rectangle`. This time, however, we need to return a stream of values, rather than a single one. +Similar to `get_feature`, `list_features`'s input is a simple message type. A `Rectangle` in this +case. This time, however, we need to return a stream of values, rather than a single one. -TODO: finish description #### Client-side streaming RPC Now let's look at something a little more complicated: the client-side streaming method @@ -492,23 +487,29 @@ Once we've implemented all our methods, we also need to start up a gRPC server s actually use our service. The following snippet shows how we do this for our `RouteGuide` service: ```rust -let addr = "[::1]:10000".parse().unwrap(); +#[tokio::main] +async fn main() -> Result<(), Box> { + let addr = "[::1]:10000".parse().unwrap(); + + let route_guide = RouteGuide { + state: State { + features: Arc::new(data::load()), + notes: Arc::new(Mutex::new(HashMap::new())), + }, + }; + + let svc = server::RouteGuideServer::new(route_guide); -let route_guide = RouteGuide { - state: State { - features: Arc::new(data::load()), - notes: Lock::new(HashMap::new()), - }, -}; + Server::builder().serve(addr, svc).await?; -let svc = server::RouteGuideServer::new(route_guide); -Server::builder().serve(addr, svc).await?; + Ok(()) +} ``` To build and start a server, we: -1. Specify the socket address to use to listen for client requests using `let addr = "[::1]:10000".parse().unwrap();`. -2. Create an instance of the gRPC server `RouteGuide {...}`. +1. Specify the socket address to use to listen for client requests +2. Create an instance of the gRPC server `RouteGuide {...}`, populating our state 3. Register our service implementation with the gRPC server `RouteGuideServer::new(...)`. 4. Call `Server::builder().serve(...)` to do a blocking wait until the process is killed. From a000abd4173d9ad05aac64b9f5b81f40bee07776 Mon Sep 17 00:00:00 2001 From: Juan Alvarez Date: Tue, 1 Oct 2019 22:04:42 -0500 Subject: [PATCH 12/29] a little love for the client --- tonic-examples/routeguide-tutorial.md | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/tonic-examples/routeguide-tutorial.md b/tonic-examples/routeguide-tutorial.md index 3925c0bb3..f4d4e4fdf 100644 --- a/tonic-examples/routeguide-tutorial.md +++ b/tonic-examples/routeguide-tutorial.md @@ -518,12 +518,19 @@ To build and start a server, we: ## Creating the client In this section, we'll look at creating a Rust client for our `RouteGuide` service. You can see our -complete example client code in TODO +complete example client code in [tonic-examples/src/routeguide/client.rs][routeguide-client] + + +[routeguide-client]: https://github.com/hyperium/tonic/blob/master/tonic-examples/src/routeguide/client.rs ### Creating a client To call service methods, we first need to create a gRPC *client* to communicate with the server. -We create this by passing the server's URL to `RouteGuideClient::connect` as follows: +Creating a client is as simple as: + +```rust +let mut client = RouteGuideClient::connect("http://[::1]:10000")?; +``` ### Calling service methods Now let's look at how we call our service methods. Note that in Tonic, RPCs are asynchronous, @@ -541,8 +548,8 @@ let response = client .await?; ``` As you can see, we call the method on the client we got earlier. In our method parameters we create -and populate a request protocol buffer object (in our case `Point`). If the call doesn't return an -error, then we can read the response information from the server from the first return value. +and populate a request protocol buffer object (in our case `Point`), and wrap it in a +`tonic::Request` #### Server-side streaming RPC From e617bef9d9b5c9de1a98aaa522f1efa0db628cbe Mon Sep 17 00:00:00 2001 From: Juan Alvarez Date: Wed, 2 Oct 2019 16:37:50 -0500 Subject: [PATCH 13/29] cleanup stubbed content --- tonic-examples/routeguide-tutorial.md | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/tonic-examples/routeguide-tutorial.md b/tonic-examples/routeguide-tutorial.md index f4d4e4fdf..af57dcfc2 100644 --- a/tonic-examples/routeguide-tutorial.md +++ b/tonic-examples/routeguide-tutorial.md @@ -509,7 +509,7 @@ async fn main() -> Result<(), Box> { To build and start a server, we: 1. Specify the socket address to use to listen for client requests -2. Create an instance of the gRPC server `RouteGuide {...}`, populating our state +2. Create an instance of the gRPC server `RouteGuide {...}`, populating our state 3. Register our service implementation with the gRPC server `RouteGuideServer::new(...)`. 4. Call `Server::builder().serve(...)` to do a blocking wait until the process is killed. @@ -553,14 +553,8 @@ and populate a request protocol buffer object (in our case `Point`), and wrap it #### Server-side streaming RPC -```rust -``` - #### Bidirectional streaming RPC -```rust -``` - ## Try it out! ### Run the server @@ -569,4 +563,4 @@ and populate a request protocol buffer object (in our case `Point`), and wrap it ## Appendix ### tonic-build configuration -### Well known Types +### Well Known Types From 1e47dfc3ab88e943d9fafbb8b23fbd5327cf4740 Mon Sep 17 00:00:00 2001 From: Juan Alvarez Date: Wed, 2 Oct 2019 16:44:46 -0500 Subject: [PATCH 14/29] simplify loading features from json file --- tonic-examples/src/routeguide/data.rs | 13 ++++--------- 1 file changed, 4 insertions(+), 9 deletions(-) diff --git a/tonic-examples/src/routeguide/data.rs b/tonic-examples/src/routeguide/data.rs index aa4cd43b3..41e6ab967 100644 --- a/tonic-examples/src/routeguide/data.rs +++ b/tonic-examples/src/routeguide/data.rs @@ -1,6 +1,5 @@ use serde::Deserialize; use std::fs::File; -use std::io::prelude::*; #[derive(Debug, Deserialize)] struct Feature { @@ -16,15 +15,11 @@ struct Location { #[allow(dead_code)] pub fn load() -> Vec { - let mut file = File::open("tonic-examples/data/route_guide_db.json") - .ok() - .expect("failed to open data file"); - let mut data = String::new(); - file.read_to_string(&mut data) - .ok() - .expect("failed to read data file"); + let file = + File::open("tonic-examples/data/route_guide_db.json").expect("failed to open data file"); - let decoded: Vec = serde_json::from_str(&data).unwrap(); + let decoded: Vec = + serde_json::from_reader(&file).expect("failed to deserialize features"); decoded .into_iter() From c3d598c327b8dbdaa0c96e09e4b71568b8b97002 Mon Sep 17 00:00:00 2001 From: Juan Alvarez Date: Wed, 2 Oct 2019 18:41:46 -0500 Subject: [PATCH 15/29] server section should be ready for review --- tonic-examples/routeguide-tutorial.md | 63 ++++++++++++++++++++------- 1 file changed, 47 insertions(+), 16 deletions(-) diff --git a/tonic-examples/routeguide-tutorial.md b/tonic-examples/routeguide-tutorial.md index af57dcfc2..19a50c566 100644 --- a/tonic-examples/routeguide-tutorial.md +++ b/tonic-examples/routeguide-tutorial.md @@ -326,11 +326,7 @@ You can find our example json data in [tonic-examples/data/route_guide_db.json][ the corresponding `data` module to load and deserialize it in [tonic-examples/routeguide/data.rs][data-module] -Lastly, we need to implement `Hash` and `Eq` for `Point` so we can use `point` values as map keys. - -[route-guide-db]: https://github.com/hyperium/tonic/blob/master/tonic-examples/data/route_guide_db.json -[data-module]: https://github.com/hyperium/tonic/blob/master/tonic-examples/src/routeguide/data.rs - +In order to use `point` values as map keys, we need to implement `Hash` and `Eq` for `Point`: ```rust impl Hash for Point { @@ -347,6 +343,21 @@ impl Eq for Point {} ``` +Lastly, we need to implement the `in_range` and `calc_distance` helper functions. You can find +them in [tonic-examples/src/routeguide/server.rs][in-range-fn] + +[route-guide-db]: https://github.com/hyperium/tonic/blob/master/tonic-examples/data/route_guide_db.json +[data-module]: https://github.com/hyperium/tonic/blob/master/tonic-examples/src/routeguide/data.rs +[in-range-fn]: https://github.com/hyperium/tonic/blob/master/tonic-examples/src/routeguide/server.rs#L188 + +#### Request and Response types +All our service methods receive a `tonic::Request` and return a +`Result, tonic::Status>`. The concrete type of `T` depends on how our methods +are declared in our *service* `.proto` definition. It can be one of two things: + +- A single value, e.g `Point`, `Widget`, `Vec` +- A stream of values, e.g. a type that implements `Stream>` + #### Simple RPC Let's look at the simplest method first, `get_feature`, which just gets a `tonic::Request` @@ -398,8 +409,12 @@ async fn list_features( } ``` -Similar to `get_feature`, `list_features`'s input is a simple message type. A `Rectangle` in this -case. This time, however, we need to return a stream of values, rather than a single one. +Like `get_feature`, `list_features`'s input is a single message. A `Rectangle` in this +case. This time, however, we need to return a stream of values, rather than a single one. +We create a channel and move the `Sink` into a new asynchronous task where we perform our +lookup, sending the features that satisfy our constraints into the channel. + +The `Stream` half of the channel is returned to the caller, wrapped in a `tonic::Response`. #### Client-side streaming RPC @@ -443,15 +458,22 @@ async fn record_route( } ``` +`record_route` is conceptually simple: we get a stream of `Points` and fold it into a `RouteSummary`. +In other words, we build a summary value as we process each `Point` in our stream, one by one. +When there are no more `Points` in our stream, we return the `RouteSummary` wrapped in a +`tonic::Response` + #### Bidirectional streaming RPC -Finally, let's look at our bidirectional streaming RPC `route_chat`. +Finally, let's look at our bidirectional streaming RPC `route_chat`, which receives a stream +of `RouteNote`s and returns a stream of `RouteNote`s. + ```rust +type RouteChatStream = Pin> + Send + 'static>>; + async fn route_chat( &self, request: Request>, ) -> Result, Status> { - println!("RouteChat"); - let stream = request.into_inner(); let mut state = self.state.clone(); @@ -481,10 +503,17 @@ async fn route_chat( } ``` +`route_chat` uses the [async-stream][async-stream] crate to perform an asynchronous transformation +from one (input) stream to another (output) stream. As the input is processed, each value is +inserted into the notes map, yielding a clone of the original `RouteNote`. The resulting stream +of notes is then returned to the caller. Neat. + +[async-stream]: https://github.com/tokio-rs/async-stream + ### Starting the server Once we've implemented all our methods, we also need to start up a gRPC server so that clients can -actually use our service. The following snippet shows how we do this for our `RouteGuide` service: +actually use our service. This is how our `main` function looks like: ```rust #[tokio::main] @@ -506,13 +535,15 @@ async fn main() -> Result<(), Box> { } ``` -To build and start a server, we: +To handle requests, `Tonic` uses [Tower][tower] and [hyper][hyper] internally. What this means, +among other things, is that we have a flexible and composable stack we can build on top of. We can, +for example, add an [interceptor][authentication-example] or implement [routing][router-example] -1. Specify the socket address to use to listen for client requests -2. Create an instance of the gRPC server `RouteGuide {...}`, populating our state -3. Register our service implementation with the gRPC server `RouteGuideServer::new(...)`. -4. Call `Server::builder().serve(...)` to do a blocking wait until the process is killed. +[tower]: https://github.com/tower-rs +[hyper]: https://github.com/hyperium/hyper +[authentication-example]: https://github.com/hyperium/tonic/blob/master/tonic-examples/src/authentication/server.rs#L54 +[router-example]: https://github.com/hyperium/tonic/blob/master/tonic-interop/src/bin/server.rs#L73 ## Creating the client From 12bf715712bd0c2d972b6455dfb7552e1019f57f Mon Sep 17 00:00:00 2001 From: Juan Alvarez Date: Wed, 2 Oct 2019 20:45:41 -0500 Subject: [PATCH 16/29] minor tweaks, wording --- tonic-examples/routeguide-tutorial.md | 26 +++++++++++++++++--------- 1 file changed, 17 insertions(+), 9 deletions(-) diff --git a/tonic-examples/routeguide-tutorial.md b/tonic-examples/routeguide-tutorial.md index 19a50c566..707954617 100644 --- a/tonic-examples/routeguide-tutorial.md +++ b/tonic-examples/routeguide-tutorial.md @@ -1,7 +1,7 @@ # gRPC Basics: Tonic This tutorial, adapted from [grpc-go][grpc-go], provides a basic introduction to working with gRPC -and tonic. By walking through this example you'll learn how to: +and Tonic. By walking through this example you'll learn how to: - Define a service in a `.proto` file. - Generate server and client code. @@ -64,6 +64,9 @@ $ cargo run --bin routeguide-client ``` **Note:** Prior to rust's 1.39 release, Tonic may be pinned to a specific toolchain version. +Consult the project's [readme][tonic-readme] for the latest info. + +[tonic-readme]: https://github.com/hyperium/tonic#getting-started ## Project setup @@ -219,7 +222,7 @@ There are two parts to making our `RouteGuide` service do its job: - Running a gRPC server to listen for requests from clients. You can find our example `RouteGuide` server in -[tonic-examples/src/routeguide/server.rs][routeguide-server] +[tonic-examples/src/routeguide/server.rs][routeguide-server]. [routeguide-server]: https://github.com/hyperium/tonic/blob/master/tonic-examples/src/routeguide/server.rs @@ -237,9 +240,10 @@ The generated code is placed inside our target directory, in a location defined environment variable that is set by cargo. For our example, this means you can find the generated code in a path similar to `target/debug/build/routeguide/out/routeguide.rs`. -You can learn more about `build.rs` the `OUT_DIR` environment variable in the [cargo book][cargo-book]. +You can learn more about `build.rs` and the `OUT_DIR` environment variable in the +[cargo book][cargo-book]. -We can bring this code into scope like this: +We can use Tonic's `include_proto` macro to bring the generated code into scope: ```rust pub mod routeguide { @@ -249,9 +253,12 @@ pub mod routeguide { use routeguide::{server, Feature, Point, Rectangle, RouteNote, RouteSummary}; ``` +**Note**: The token passed to the `include_proto` macro (in our case "routeguide") is the name of +the package declared in in our `.proto` file, not a filename, e.g "routeguide.rs". + [cargo-book]: https://doc.rust-lang.org/cargo/reference/environment-variables.html#environment-variables-cargo-sets-for-build-scripts -Now we are ready to stub out our service: +We are now we are ready to stub out our service implementation: ```rust #[tonic::async_trait] @@ -324,9 +331,9 @@ $ touch src/data.rs You can find our example json data in [tonic-examples/data/route_guide_db.json][route-guide-db] and the corresponding `data` module to load and deserialize it in -[tonic-examples/routeguide/data.rs][data-module] +[tonic-examples/routeguide/data.rs][data-module]. -In order to use `point` values as map keys, we need to implement `Hash` and `Eq` for `Point`: +Next, we need to implement `Hash` and `Eq` for `Point`, so we can use point values as map keys: ```rust impl Hash for Point { @@ -343,8 +350,9 @@ impl Eq for Point {} ``` -Lastly, we need to implement the `in_range` and `calc_distance` helper functions. You can find -them in [tonic-examples/src/routeguide/server.rs][in-range-fn] +Lastly, we need wo helper functions: `in_range` and `calc_distance`. We will need them when +performing feature lookups. You can find them in +[tonic-examples/src/routeguide/server.rs][in-range-fn]. [route-guide-db]: https://github.com/hyperium/tonic/blob/master/tonic-examples/data/route_guide_db.json [data-module]: https://github.com/hyperium/tonic/blob/master/tonic-examples/src/routeguide/data.rs From 5e6d717b5d4680691e0d288d6395c0ab0f901c88 Mon Sep 17 00:00:00 2001 From: Juan Alvarez Date: Wed, 2 Oct 2019 23:47:40 -0500 Subject: [PATCH 17/29] client setup --- tonic-examples/routeguide-tutorial.md | 53 +++++++++++++++++++++------ 1 file changed, 42 insertions(+), 11 deletions(-) diff --git a/tonic-examples/routeguide-tutorial.md b/tonic-examples/routeguide-tutorial.md index 707954617..48385864e 100644 --- a/tonic-examples/routeguide-tutorial.md +++ b/tonic-examples/routeguide-tutorial.md @@ -556,27 +556,53 @@ for example, add an [interceptor][authentication-example] or implement [routing] ## Creating the client -In this section, we'll look at creating a Rust client for our `RouteGuide` service. You can see our -complete example client code in [tonic-examples/src/routeguide/client.rs][routeguide-client] +In this section, we'll look at creating a Tonic client for our `RouteGuide` service. You can see our +complete example client code in [tonic-examples/src/routeguide/client.rs][routeguide-client]. +Our crate will have two binary targets: `routeguide-client` and `routeguide-server`. We need to +edit our `Cargo.toml` accordingly: -[routeguide-client]: https://github.com/hyperium/tonic/blob/master/tonic-examples/src/routeguide/client.rs +```toml +[[bin]] +name = "routeguide-server" +path = "src/server.rs" + +[[bin]] +name = "routeguide-client" +path = "src/client.rs" +``` + +Next, rename `main.rs` to `server.rs` and create a new file `client.rs`. -### Creating a client +```shell +$ mv src/main.rs src/server.rs +$ touch src/client.rs +``` To call service methods, we first need to create a gRPC *client* to communicate with the server. -Creating a client is as simple as: ```rust -let mut client = RouteGuideClient::connect("http://[::1]:10000")?; +pub mod route_guide { + tonic::include_proto!("routeguide"); +} + +use route_guide::client::RouteGuideClient; + +#[tokio::main] +async fn main() -> Result<(), Box> { + let mut client = RouteGuideClient::connect("http://[::1]:10000")?; +} ``` +[routeguide-client]: https://github.com/hyperium/tonic/blob/master/tonic-examples/src/routeguide/client.rs + + ### Calling service methods Now let's look at how we call our service methods. Note that in Tonic, RPCs are asynchronous, -which means that the RPC call needs to be awaited. +which means that the RPC call needs to be `awaited`. #### Simple RPC -Calling the simple RPC `GetFeature` is nearly as straightforward as calling a local method. +Calling the simple RPC `GetFeature` is as straightforward as calling a local method. ```rust let response = client @@ -586,9 +612,7 @@ let response = client })) .await?; ``` -As you can see, we call the method on the client we got earlier. In our method parameters we create -and populate a request protocol buffer object (in our case `Point`), and wrap it in a -`tonic::Request` +We call the `get_feature` client, passing a `Point` value wrapped in a `tonic::Request`. #### Server-side streaming RPC @@ -597,9 +621,16 @@ and populate a request protocol buffer object (in our case `Point`), and wrap it ## Try it out! ### Run the server +```shell +$ cargo run --bin routeguide-server +``` ### Run the client +```shell +$ cargo run --bin routeguide-client +``` ## Appendix ### tonic-build configuration ### Well Known Types + From b0182734c1c99f8831f669e516d4d724620e48c4 Mon Sep 17 00:00:00 2001 From: Juan Alvarez Date: Wed, 2 Oct 2019 23:48:58 -0500 Subject: [PATCH 18/29] example: implement client list_features --- tonic-examples/src/routeguide/client.rs | 70 +++++++++++++++++++------ 1 file changed, 55 insertions(+), 15 deletions(-) diff --git a/tonic-examples/src/routeguide/client.rs b/tonic-examples/src/routeguide/client.rs index f5ebd34fe..f62fd323e 100644 --- a/tonic-examples/src/routeguide/client.rs +++ b/tonic-examples/src/routeguide/client.rs @@ -1,7 +1,9 @@ use futures::TryStreamExt; -use route_guide::{Point, RouteNote}; +use route_guide::{Point, Rectangle, RouteNote}; +use std::error::Error; use std::time::{Duration, Instant}; use tokio::timer::Interval; +use tonic::transport::Channel; use tonic::Request; pub mod route_guide { @@ -10,23 +12,34 @@ pub mod route_guide { use route_guide::client::RouteGuideClient; -#[tokio::main] -async fn main() -> Result<(), Box> { - let mut client = RouteGuideClient::connect("http://[::1]:10000")?; +async fn print_feature( + point: Point, + client: &mut RouteGuideClient, +) -> Result<(), Box> { + let response = client.get_feature(Request::new(point)).await?; + println!("FEATURE = {:?}", response); - let start = Instant::now(); + Ok(()) +} - let response = client - .get_feature(Request::new(Point { - latitude: 409146138, - longitude: -746188906, - })) - .await?; +async fn print_features( + rect: Rectangle, + client: &mut RouteGuideClient, +) -> Result<(), Box> { + let mut stream = client.list_features(Request::new(rect)).await?.into_inner(); - println!("FEATURE = {:?}", response); + while let Some(feature) = stream.try_next().await? { + println!("NOTE = {:?}", feature); + } + + Ok(()) +} + +async fn route_chat(client: &mut RouteGuideClient) -> Result<(), Box> { + let start = Instant::now(); let outbound = async_stream::try_stream! { - let mut interval = Interval::new_interval(Duration::from_secs(1)); + let mut interval = Interval::new_interval(Duration::from_secs(1)); while let Some(time) = interval.next().await { let elapsed = time.duration_since(start); @@ -43,9 +56,7 @@ async fn main() -> Result<(), Box> { }; let request = Request::new(outbound); - let response = client.route_chat(request).await?; - let mut inbound = response.into_inner(); while let Some(note) = inbound.try_next().await? { @@ -54,3 +65,32 @@ async fn main() -> Result<(), Box> { Ok(()) } + +#[tokio::main] +async fn main() -> Result<(), Box> { + let mut client = RouteGuideClient::connect("http://[::1]:10000")?; + + let point = Point { + latitude: 409146138, + longitude: -746188906, + }; + + print_feature(point, &mut client).await?; + + let rectangle = Rectangle { + lo: Some(Point { + latitude: 400000000, + longitude: -750000000, + }), + hi: Some(Point { + latitude: 420000000, + longitude: -730000000, + }), + }; + + print_features(rectangle, &mut client).await?; + + route_chat(&mut client).await?; + + Ok(()) +} From 1ad36b1f4249d77befa4a1cac53a38954aeeca91 Mon Sep 17 00:00:00 2001 From: Juan Alvarez Date: Fri, 4 Oct 2019 23:45:28 -0500 Subject: [PATCH 19/29] tweaks to server, a bit more client --- tonic-examples/routeguide-tutorial.md | 138 +++++++++++++++++------- tonic-examples/src/routeguide/client.rs | 22 ++-- 2 files changed, 107 insertions(+), 53 deletions(-) diff --git a/tonic-examples/routeguide-tutorial.md b/tonic-examples/routeguide-tutorial.md index 48385864e..2a0a557a6 100644 --- a/tonic-examples/routeguide-tutorial.md +++ b/tonic-examples/routeguide-tutorial.md @@ -39,7 +39,7 @@ To run the sample code and walk through the tutorial, the only prerequisite is R Clone or download Tonic's repository: ```shell -git clone https://github.com/hyperium/tonic.git +$ git clone https://github.com/hyperium/tonic.git ``` Change your current directory to Tonic's repository root: @@ -171,17 +171,18 @@ and our `.proto` definitions in sync. Behind the scenes, Tonic uses [PROST!][prost] to handle protocol buffer serialization and code generation. -Edit `Cargo.toml` to add all the dependencies we'll need for this example: +Edit `Cargo.toml` and add all the dependencies we'll need for this example: ```toml [dependencies] -tonic = "0.1.0-alpha.1" -futures-preview = { version = "0.3.0-alpha.19", default-features = false, features = ["alloc"]} -tokio = "0.2.0-alpha.6" -prost = "0.5" +async-stream = "0.1.2" bytes = "0.4" -serde_json = "1.0" +futures-preview = { version = "0.3.0-alpha.19", default-features = false, features = ["alloc"]} serde = { version = "1.0", features = ["derive"] } +serde_json = "1.0" +prost = "0.5" +tokio = "0.2.0-alpha.6" +tonic = "0.1.0-alpha.1" [build-dependencies] tonic-build = "0.1.0-alpha.1" @@ -191,7 +192,8 @@ Create a `build.rs` file at the root of your crate: ```rust fn main() { - tonic_build::compile_protos("proto/route_guide.proto").unwrap(); + tonic_build::compile_protos("proto/route_guide.proto") + .unwrap_or_else(|e| panic!("Failed to compile protos {:?}", e)); } ``` @@ -235,7 +237,7 @@ We can start by defining a struct to represent our service, we can do this on `m struct RouteGuide; ``` -We now need to implement the `server::RouteGuide` trait that is generated in our build step. +Next, we need to implement the `server::RouteGuide` trait that is generated in our build step. The generated code is placed inside our target directory, in a location defined by the `OUT_DIR` environment variable that is set by cargo. For our example, this means you can find the generated code in a path similar to `target/debug/build/routeguide/out/routeguide.rs`. @@ -258,7 +260,7 @@ the package declared in in our `.proto` file, not a filename, e.g "routeguide.rs [cargo-book]: https://doc.rust-lang.org/cargo/reference/environment-variables.html#environment-variables-cargo-sets-for-build-scripts -We are now we are ready to stub out our service implementation: +With this in place, we can stub out our service implementation: ```rust #[tonic::async_trait] @@ -294,8 +296,8 @@ impl server::RouteGuide for RouteGuide { } ``` -**Note**: The `tonic::async_trait` attribute macro adds support for async fn in traits. It uses -[async-trait][async-trait] internally. +**Note**: The `tonic::async_trait` attribute macro adds support for async functions in traits. It +uses [async-trait][async-trait] internally. [async-trait]: https://github.com/dtolnay/async-trait @@ -350,9 +352,8 @@ impl Eq for Point {} ``` -Lastly, we need wo helper functions: `in_range` and `calc_distance`. We will need them when -performing feature lookups. You can find them in -[tonic-examples/src/routeguide/server.rs][in-range-fn]. +Lastly, we need two helper functions: `in_range` and `calc_distance`. We'll use them when performing +feature lookups. You can find them in [tonic-examples/src/routeguide/server.rs][in-range-fn]. [route-guide-db]: https://github.com/hyperium/tonic/blob/master/tonic-examples/data/route_guide_db.json [data-module]: https://github.com/hyperium/tonic/blob/master/tonic-examples/src/routeguide/data.rs @@ -361,16 +362,15 @@ performing feature lookups. You can find them in #### Request and Response types All our service methods receive a `tonic::Request` and return a `Result, tonic::Status>`. The concrete type of `T` depends on how our methods -are declared in our *service* `.proto` definition. It can be one of two things: - -- A single value, e.g `Point`, `Widget`, `Vec` -- A stream of values, e.g. a type that implements `Stream>` +are declared in our *service* `.proto` definition. It can be either: +- A single value, e.g `Point`, `Vec` +- A stream of values, e.g. `impl Stream>` #### Simple RPC Let's look at the simplest method first, `get_feature`, which just gets a `tonic::Request` -from the client and tries to find a feature at the location represented by the given `Point`. -If no feature is found, it returns an empty one. +from the client and tries to find a feature at the given `Point`. If no feature is found, it returns +an empty one. ```rust async fn get_feature(&self, request: Request) -> Result, Status> { @@ -417,10 +417,10 @@ async fn list_features( } ``` -Like `get_feature`, `list_features`'s input is a single message. A `Rectangle` in this +Like `get_feature`, `list_features`'s input is a single message, a `Rectangle` in this case. This time, however, we need to return a stream of values, rather than a single one. -We create a channel and move the `Sink` into a new asynchronous task where we perform our -lookup, sending the features that satisfy our constraints into the channel. +We create a channel and spawn a new asynchronous task where we perform a lookup, sending +the features that satisfy our constraints into the channel. The `Stream` half of the channel is returned to the caller, wrapped in a `tonic::Response`. @@ -429,7 +429,7 @@ The `Stream` half of the channel is returned to the caller, wrapped in a `tonic: Now let's look at something a little more complicated: the client-side streaming method `record_route`, where we get a stream of `Point`s from the client and return a single `RouteSummary` with information about their trip. As you can see, this time the method receives a -`tonic::Request>` +`tonic::Request>`. ```rust async fn record_route( @@ -507,14 +507,14 @@ async fn route_chat( as Pin< Box> + Send + 'static>, >)) - } + } ``` `route_chat` uses the [async-stream][async-stream] crate to perform an asynchronous transformation from one (input) stream to another (output) stream. As the input is processed, each value is inserted into the notes map, yielding a clone of the original `RouteNote`. The resulting stream -of notes is then returned to the caller. Neat. +is then returned to the caller. Neat. [async-stream]: https://github.com/tokio-rs/async-stream @@ -545,7 +545,8 @@ async fn main() -> Result<(), Box> { To handle requests, `Tonic` uses [Tower][tower] and [hyper][hyper] internally. What this means, among other things, is that we have a flexible and composable stack we can build on top of. We can, -for example, add an [interceptor][authentication-example] or implement [routing][router-example] +for example, add an [interceptor][authentication-example] or implement [routing][router-example]. +In the future, Tonic will include higher level support for routing and interceptors. [tower]: https://github.com/tower-rs @@ -572,7 +573,7 @@ name = "routeguide-client" path = "src/client.rs" ``` -Next, rename `main.rs` to `server.rs` and create a new file `client.rs`. +Rename `main.rs` to `server.rs` and create a new file `client.rs`. ```shell $ mv src/main.rs src/server.rs @@ -586,14 +587,21 @@ pub mod route_guide { tonic::include_proto!("routeguide"); } -use route_guide::client::RouteGuideClient; +use route_guide::{client::RouteGuideClient, Point, Rectangle, RouteNote}; #[tokio::main] async fn main() -> Result<(), Box> { let mut client = RouteGuideClient::connect("http://[::1]:10000")?; + + Ok(()) } ``` +Same as in the server implementation, we start by bringing our generated code into scope. We then +create a client in our main function, passing the server's full URL to 'RouteGuideClient::connect`. +Our client is now ready to make service calls. Note that client is mutable, this is because it needs +to manage internal state. + [routeguide-client]: https://github.com/hyperium/tonic/blob/master/tonic-examples/src/routeguide/client.rs @@ -602,22 +610,81 @@ Now let's look at how we call our service methods. Note that in Tonic, RPCs are which means that the RPC call needs to be `awaited`. #### Simple RPC -Calling the simple RPC `GetFeature` is as straightforward as calling a local method. +Calling the simple RPC `get_feature` is as straightforward as calling a local method. ```rust -let response = client +client .get_feature(Request::new(Point { latitude: 409146138, longitude: -746188906, })) .await?; ``` -We call the `get_feature` client, passing a `Point` value wrapped in a `tonic::Request`. +We call the `get_feature` client method, passing a `Point` value wrapped in a `tonic::Request`. #### Server-side streaming RPC +Here's where we call the server-side streaming method `list_features`, which returns a stream of +geographical `Feature`s. + +```rust +async fn print_features( + rect: Rectangle, + client: &mut RouteGuideClient, +) -> Result<(), Box> { + let mut stream = client.list_features(Request::new(rect)).await?.into_inner(); + + while let Some(feature) = stream.try_next().await? { + println!("NOTE = {:?}", feature); + } + + Ok(()) +} +``` + +As in the simple RPC, we pass a single value request. However, instead of getting a +single value back, we get a stream of `Features`. + +We use the `TryStreamExt`'s `try_next()` method to repeatedly read in the server's +responses to a response protocol buffer object (in this case a `Feature`) until there are no more +messages. + +#### Client-side streaming RPC #### Bidirectional streaming RPC +```rust +async fn route_chat(client: &mut RouteGuideClient) -> Result<(), Box> { + let start = Instant::now(); + + let outbound = async_stream::try_stream! { + let mut interval = Interval::new_interval(Duration::from_secs(1)); + + while let Some(time) = interval.next().await { + let elapsed = time.duration_since(start); + let note = RouteNote { + location: Some(Point { + latitude: 409146138 + elapsed.as_secs() as i32, + longitude: -746188906, + }), + message: format!("at {:?}", elapsed), + }; + + yield note; + } + }; + + let request = Request::new(outbound); + let response = client.route_chat(request).await?; + let mut inbound = response.into_inner(); + + while let Some(note) = inbound.try_next().await? { + println!("NOTE = {:?}", note); + } + + Ok(()) +} + +``` ## Try it out! ### Run the server @@ -629,8 +696,3 @@ $ cargo run --bin routeguide-server ```shell $ cargo run --bin routeguide-client ``` - -## Appendix -### tonic-build configuration -### Well Known Types - diff --git a/tonic-examples/src/routeguide/client.rs b/tonic-examples/src/routeguide/client.rs index f62fd323e..000f5ceba 100644 --- a/tonic-examples/src/routeguide/client.rs +++ b/tonic-examples/src/routeguide/client.rs @@ -12,16 +12,6 @@ pub mod route_guide { use route_guide::client::RouteGuideClient; -async fn print_feature( - point: Point, - client: &mut RouteGuideClient, -) -> Result<(), Box> { - let response = client.get_feature(Request::new(point)).await?; - println!("FEATURE = {:?}", response); - - Ok(()) -} - async fn print_features( rect: Rectangle, client: &mut RouteGuideClient, @@ -70,12 +60,14 @@ async fn route_chat(client: &mut RouteGuideClient) -> Result<(), Box Result<(), Box> { let mut client = RouteGuideClient::connect("http://[::1]:10000")?; - let point = Point { - latitude: 409146138, - longitude: -746188906, - }; + let response = client + .get_feature(Request::new(Point { + latitude: 409146138, + longitude: -746188906, + })) + .await?; - print_feature(point, &mut client).await?; + println!("RESPONSE = {:?}", response); let rectangle = Rectangle { lo: Some(Point { From 6979497fad280ff0751f0ca7bcdbe79c6ec1734a Mon Sep 17 00:00:00 2001 From: Juan Alvarez Date: Fri, 4 Oct 2019 23:50:13 -0500 Subject: [PATCH 20/29] typo --- tonic-examples/routeguide-tutorial.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tonic-examples/routeguide-tutorial.md b/tonic-examples/routeguide-tutorial.md index 2a0a557a6..758f7e975 100644 --- a/tonic-examples/routeguide-tutorial.md +++ b/tonic-examples/routeguide-tutorial.md @@ -598,7 +598,7 @@ async fn main() -> Result<(), Box> { ``` Same as in the server implementation, we start by bringing our generated code into scope. We then -create a client in our main function, passing the server's full URL to 'RouteGuideClient::connect`. +create a client in our main function, passing the server's full URL to `RouteGuideClient::connect`. Our client is now ready to make service calls. Note that client is mutable, this is because it needs to manage internal state. From ea666ff6c893a0d8f82f4cfb75d164756c73309d Mon Sep 17 00:00:00 2001 From: Juan Alvarez Date: Sat, 5 Oct 2019 14:22:28 -0500 Subject: [PATCH 21/29] tutorial ready for review --- tonic-examples/Cargo.toml | 1 + tonic-examples/routeguide-tutorial.md | 146 +++++++++++++++++++----- tonic-examples/src/routeguide/client.rs | 76 ++++++++---- 3 files changed, 173 insertions(+), 50 deletions(-) diff --git a/tonic-examples/Cargo.toml b/tonic-examples/Cargo.toml index 141a6e0c6..d79c681be 100644 --- a/tonic-examples/Cargo.toml +++ b/tonic-examples/Cargo.toml @@ -58,6 +58,7 @@ tower = "=0.3.0-alpha.2" # Required for routeguide serde = { version = "1.0", features = ["derive"] } serde_json = "1.0" +rand = "0.7.2" [build-dependencies] tonic-build = { path = "../tonic-build" } diff --git a/tonic-examples/routeguide-tutorial.md b/tonic-examples/routeguide-tutorial.md index 758f7e975..913b36d7a 100644 --- a/tonic-examples/routeguide-tutorial.md +++ b/tonic-examples/routeguide-tutorial.md @@ -1,18 +1,18 @@ # gRPC Basics: Tonic -This tutorial, adapted from [grpc-go][grpc-go], provides a basic introduction to working with gRPC +This tutorial, adapted from [grpc-go], provides a basic introduction to working with gRPC and Tonic. By walking through this example you'll learn how to: - Define a service in a `.proto` file. - Generate server and client code. - Write a simple client and server for your service. -It assumes you are familiar with [protocol buffers][protobuf] and Rust. Note that the example in +It assumes you are familiar with [protocol buffers] and Rust. Note that the example in this tutorial uses the proto3 version of the protocol buffers language, you can find out more in the [proto3 language guide][proto3]. [grpc-go]: https://github.com/grpc/grpc-go/blob/master/examples/gotutorial.md -[protobuf]: https://developers.google.com/protocol-buffers/docs/overview +[protocol buffers]: https://developers.google.com/protocol-buffers/docs/overview [proto3]: https://developers.google.com/protocol-buffers/docs/proto3 ## Why use gRPC? @@ -30,7 +30,7 @@ protocol buffers, including efficient serialization, a simple IDL, and easy inte ## Prerequisites To run the sample code and walk through the tutorial, the only prerequisite is Rust itself. -[rustup][rustup] is a convenient tool to install it, if you haven't already. +[rustup] is a convenient tool to install it, if you haven't already. [rustup]: https://rustup.rs @@ -64,9 +64,9 @@ $ cargo run --bin routeguide-client ``` **Note:** Prior to rust's 1.39 release, Tonic may be pinned to a specific toolchain version. -Consult the project's [readme][tonic-readme] for the latest info. +Consult the project's [readme] for the latest info. -[tonic-readme]: https://github.com/hyperium/tonic#getting-started +[readme]: https://github.com/hyperium/tonic#getting-started ## Project setup @@ -81,10 +81,9 @@ $ cd routeguide ## Defining the service Our first step is to define the gRPC *service* and the method *request* and *response* types using -[protocol buffers][protobuf]. We will keep our `.proto` files in a directory in our crate's root. +[protocol buffers]. We will keep our `.proto` files in a directory in our crate's root. Note that Tonic does not really care where our `.proto` definitions live. We will see how to use -different code generation configuration later in the tutorial. - +different [code generation configuration](#tonic-build) later in the tutorial. ```shell $ mkdir proto && touch proto/route_guide.proto @@ -93,8 +92,6 @@ $ mkdir proto && touch proto/route_guide.proto You can see the complete `.proto` file in [tonic-examples/proto/routeguide/route_guide.proto][routeguide-proto]. -[routeguide-proto]: https://github.com/hyperium/tonic/blob/master/tonic-examples/proto/routeguide/route_guide.proto - To define a service, you specify a named `service` in your `.proto` file: ```proto @@ -161,6 +158,7 @@ message Point { } ``` +[routeguide-proto]: https://github.com/hyperium/tonic/blob/master/tonic-examples/proto/routeguide/route_guide.proto ## Generating client and server code @@ -168,7 +166,7 @@ Tonic can be configured to generate code as part cargo's normal build process. T convenient because once we've set everything up, there is no extra step to keep the generated code and our `.proto` definitions in sync. -Behind the scenes, Tonic uses [PROST!][prost] to handle protocol buffer serialization and code +Behind the scenes, Tonic uses [PROST!] to handle protocol buffer serialization and code generation. Edit `Cargo.toml` and add all the dependencies we'll need for this example: @@ -181,6 +179,7 @@ futures-preview = { version = "0.3.0-alpha.19", default-features = false, featur serde = { version = "1.0", features = ["derive"] } serde_json = "1.0" prost = "0.5" +rand = "0.7.2" tokio = "0.2.0-alpha.6" tonic = "0.1.0-alpha.1" @@ -197,8 +196,6 @@ fn main() { } ``` -[prost]: https://github.com/danburkert/prost - ```shell $ cargo build ``` @@ -212,6 +209,8 @@ That's it. The generated code contains: If your are curious as to where the generated files are, keep reading. The mystery will be revealed. We can now move on to the fun part. +[PROST!]: https://github.com/danburkert/prost + ## Creating the server First let's look at how we create a `RouteGuide` server. If you're only interested in creating gRPC @@ -242,8 +241,7 @@ The generated code is placed inside our target directory, in a location defined environment variable that is set by cargo. For our example, this means you can find the generated code in a path similar to `target/debug/build/routeguide/out/routeguide.rs`. -You can learn more about `build.rs` and the `OUT_DIR` environment variable in the -[cargo book][cargo-book]. +You can learn more about `build.rs` and the `OUT_DIR` environment variable in the [cargo book]. We can use Tonic's `include_proto` macro to bring the generated code into scope: @@ -258,8 +256,6 @@ use routeguide::{server, Feature, Point, Rectangle, RouteNote, RouteSummary}; **Note**: The token passed to the `include_proto` macro (in our case "routeguide") is the name of the package declared in in our `.proto` file, not a filename, e.g "routeguide.rs". -[cargo-book]: https://doc.rust-lang.org/cargo/reference/environment-variables.html#environment-variables-cargo-sets-for-build-scripts - With this in place, we can stub out our service implementation: ```rust @@ -297,8 +293,9 @@ impl server::RouteGuide for RouteGuide { ``` **Note**: The `tonic::async_trait` attribute macro adds support for async functions in traits. It -uses [async-trait][async-trait] internally. +uses [async-trait] internally. +[cargo book]: https://doc.rust-lang.org/cargo/reference/environment-variables.html#environment-variables-cargo-sets-for-build-scripts [async-trait]: https://github.com/dtolnay/async-trait ### Server state @@ -335,6 +332,9 @@ You can find our example json data in [tonic-examples/data/route_guide_db.json][ the corresponding `data` module to load and deserialize it in [tonic-examples/routeguide/data.rs][data-module]. +**Note** If you are following along, you'll need to change the data file's path from +`tonic-examples/data/route_guide_db.json` to `data/route_guide_db.json`. + Next, we need to implement `Hash` and `Eq` for `Point`, so we can use point values as map keys: ```rust @@ -511,7 +511,7 @@ async fn route_chat( } ``` -`route_chat` uses the [async-stream][async-stream] crate to perform an asynchronous transformation +`route_chat` uses the [async-stream] crate to perform an asynchronous transformation from one (input) stream to another (output) stream. As the input is processed, each value is inserted into the notes map, yielding a clone of the original `RouteNote`. The resulting stream is then returned to the caller. Neat. @@ -543,13 +543,13 @@ async fn main() -> Result<(), Box> { } ``` -To handle requests, `Tonic` uses [Tower][tower] and [hyper][hyper] internally. What this means, +To handle requests, `Tonic` uses [Tower] and [hyper] internally. What this means, among other things, is that we have a flexible and composable stack we can build on top of. We can, for example, add an [interceptor][authentication-example] or implement [routing][router-example]. In the future, Tonic will include higher level support for routing and interceptors. -[tower]: https://github.com/tower-rs +[Tower]: https://github.com/tower-rs [hyper]: https://github.com/hyperium/hyper [authentication-example]: https://github.com/hyperium/tonic/blob/master/tonic-examples/src/authentication/server.rs#L54 [router-example]: https://github.com/hyperium/tonic/blob/master/tonic-interop/src/bin/server.rs#L73 @@ -620,18 +620,30 @@ client })) .await?; ``` -We call the `get_feature` client method, passing a `Point` value wrapped in a `tonic::Request`. +We call the `get_feature` client method, passing a single `Point` value wrapped in a +`tonic::Request`. We get a `Result, tonic::Status>' back. #### Server-side streaming RPC Here's where we call the server-side streaming method `list_features`, which returns a stream of geographical `Feature`s. ```rust -async fn print_features( - rect: Rectangle, - client: &mut RouteGuideClient, -) -> Result<(), Box> { - let mut stream = client.list_features(Request::new(rect)).await?.into_inner(); +async fn print_features(client: &mut RouteGuideClient) -> Result<(), Box> { + let rectangle = Rectangle { + lo: Some(Point { + latitude: 400000000, + longitude: -750000000, + }), + hi: Some(Point { + latitude: 420000000, + longitude: -730000000, + }), + }; + + let mut stream = client + .list_features(Request::new(rectangle)) + .await? + .into_inner(); while let Some(feature) = stream.try_next().await? { println!("NOTE = {:?}", feature); @@ -649,11 +661,42 @@ responses to a response protocol buffer object (in this case a `Feature`) until messages. #### Client-side streaming RPC +The client-side streaming method `record_route` takes a stream of `Point`s and returns a single +`RouteSummary` value. + +```rust +async fn run_record_route(client: &mut RouteGuideClient) -> Result<(), Box> { + let mut rng = rand::thread_rng(); + let point_count = rng.gen_range(2, 100); + + let mut points = vec![]; + for _ in 0..=point_count { + points.push(Ok(random_point(&mut rng))) + } + + println!("Traversing {} points", points.len()); + let request = Request::new(stream::iter(points)); + + match client.record_route(request).await { + Ok(response) => println!("SUMMARY: {:?}", response.into_inner()), + Err(e) => println!("something went wrong: {:?}", e), + } + + Ok(()) +} +``` +We build a vector of a random number of `Result` values (between 2 and 100) and then convert +it into a `Stream` using the `futures::stream::iter` function. The resulting stream is then +wrapped in a `tonic::Request`. +We then match on the returned `Result`, printing the `RouteSummary` or an error. + #### Bidirectional streaming RPC +Finally, let's look at our bidirectional streaming RPC. The `route_chat` method takes a stream +of `RouteNotes` and returns either another stream of `RouteNotes` or an error. ```rust -async fn route_chat(client: &mut RouteGuideClient) -> Result<(), Box> { +async fn run_route_chat(client: &mut RouteGuideClient) -> Result<(), Box> { let start = Instant::now(); let outbound = async_stream::try_stream! { @@ -683,8 +726,11 @@ async fn route_chat(client: &mut RouteGuideClient) -> Result<(), Box +### tonic_build configuration + +Tonic's default code generation configuration is convenient for self contained examples and small +projects. However, there are some cases when we need a slightly different workflow. For example: + +- When building rust clients and servers in different crates. +- When building a rust client or server (or both) as part of a larger, multi-language. +project. + +In general, whenever we want to keep our `.proto` definitions in a central place and generate +code for different crates or different languages, the default configuration is not enough. + +Luckily, `tonic_build` can be configured to fit whatever workflow we need. Here are just two +possibilities: + +1) We can keep our `.proto` definitions in a separate crate and generate our code on demand, as +opposed to at build time, placing the resulting modules wherever we need them. + +`main.rs` + +```rust +fn main() { + tonic_build::configure() + .build_client(false) + .out_dir("another_crate/src/pb") + .compile(&["path/my_proto.proto"], &["path"]) + .expect("failed to compile protos"); +} +``` + +On `cargo run`, this will generate code for the client only, and place the resulting file in +`another_crate/src/pb`. + +2) Similarly, we could also keep the `.proto` definitions in a separate crate and then use that +crate as a direct dependency wherever we need it. + diff --git a/tonic-examples/src/routeguide/client.rs b/tonic-examples/src/routeguide/client.rs index 000f5ceba..3372e83bb 100644 --- a/tonic-examples/src/routeguide/client.rs +++ b/tonic-examples/src/routeguide/client.rs @@ -1,4 +1,6 @@ -use futures::TryStreamExt; +use futures::{stream, TryStreamExt}; +use rand::rngs::ThreadRng; +use rand::Rng; use route_guide::{Point, Rectangle, RouteNote}; use std::error::Error; use std::time::{Duration, Instant}; @@ -12,11 +14,22 @@ pub mod route_guide { use route_guide::client::RouteGuideClient; -async fn print_features( - rect: Rectangle, - client: &mut RouteGuideClient, -) -> Result<(), Box> { - let mut stream = client.list_features(Request::new(rect)).await?.into_inner(); +async fn print_features(client: &mut RouteGuideClient) -> Result<(), Box> { + let rectangle = Rectangle { + lo: Some(Point { + latitude: 400000000, + longitude: -750000000, + }), + hi: Some(Point { + latitude: 420000000, + longitude: -730000000, + }), + }; + + let mut stream = client + .list_features(Request::new(rectangle)) + .await? + .into_inner(); while let Some(feature) = stream.try_next().await? { println!("NOTE = {:?}", feature); @@ -25,7 +38,27 @@ async fn print_features( Ok(()) } -async fn route_chat(client: &mut RouteGuideClient) -> Result<(), Box> { +async fn run_record_route(client: &mut RouteGuideClient) -> Result<(), Box> { + let mut rng = rand::thread_rng(); + let point_count = rng.gen_range(2, 100); + + let mut points = vec![]; + for _ in 0..=point_count { + points.push(Ok(random_point(&mut rng))) + } + + println!("Traversing {} points", points.len()); + let request = Request::new(stream::iter(points)); + + match client.record_route(request).await { + Ok(response) => println!("SUMMARY: {:?}", response.into_inner()), + Err(e) => println!("something went wrong: {:?}", e), + } + + Ok(()) +} + +async fn run_route_chat(client: &mut RouteGuideClient) -> Result<(), Box> { let start = Instant::now(); let outbound = async_stream::try_stream! { @@ -60,29 +93,32 @@ async fn route_chat(client: &mut RouteGuideClient) -> Result<(), Box Result<(), Box> { let mut client = RouteGuideClient::connect("http://[::1]:10000")?; + println!("*** SIMPLE RPC ***"); let response = client .get_feature(Request::new(Point { latitude: 409146138, longitude: -746188906, })) .await?; - println!("RESPONSE = {:?}", response); - let rectangle = Rectangle { - lo: Some(Point { - latitude: 400000000, - longitude: -750000000, - }), - hi: Some(Point { - latitude: 420000000, - longitude: -730000000, - }), - }; + println!("\n*** SERVER STREAMING ***"); + print_features(&mut client).await?; - print_features(rectangle, &mut client).await?; + println!("\n*** CLIENT STREAMING ***"); + run_record_route(&mut client).await?; - route_chat(&mut client).await?; + println!("\n*** BIDIRECTIONAL STREAMING ***"); + run_route_chat(&mut client).await?; Ok(()) } + +fn random_point(rng: &mut ThreadRng) -> Point { + let latitude = (rng.gen_range(0, 180) - 90) * 10_000_000; + let longitude = (rng.gen_range(0, 360) - 180) * 10_000_000; + Point { + latitude, + longitude, + } +} From 6a452cabeafdb5b9ab9c9edb8818a06c26b52b1f Mon Sep 17 00:00:00 2001 From: Juan Alvarez Date: Sat, 5 Oct 2019 14:26:13 -0500 Subject: [PATCH 22/29] typo --- tonic-examples/routeguide-tutorial.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tonic-examples/routeguide-tutorial.md b/tonic-examples/routeguide-tutorial.md index 913b36d7a..50d788f67 100644 --- a/tonic-examples/routeguide-tutorial.md +++ b/tonic-examples/routeguide-tutorial.md @@ -621,7 +621,7 @@ client .await?; ``` We call the `get_feature` client method, passing a single `Point` value wrapped in a -`tonic::Request`. We get a `Result, tonic::Status>' back. +`tonic::Request`. We get a `Result, tonic::Status>` back. #### Server-side streaming RPC Here's where we call the server-side streaming method `list_features`, which returns a stream of From 920426f563a86d20063c769df5c82519fde7fdc7 Mon Sep 17 00:00:00 2001 From: Juan Alvarez Date: Sun, 6 Oct 2019 14:15:34 -0500 Subject: [PATCH 23/29] server section tweaks --- tonic-examples/routeguide-tutorial.md | 34 +++++++++++++++++---------- 1 file changed, 22 insertions(+), 12 deletions(-) diff --git a/tonic-examples/routeguide-tutorial.md b/tonic-examples/routeguide-tutorial.md index 50d788f67..21e6a7ad9 100644 --- a/tonic-examples/routeguide-tutorial.md +++ b/tonic-examples/routeguide-tutorial.md @@ -7,7 +7,7 @@ and Tonic. By walking through this example you'll learn how to: - Generate server and client code. - Write a simple client and server for your service. -It assumes you are familiar with [protocol buffers] and Rust. Note that the example in +It assumes you are familiar with [protocol buffers] and basic Rust. Note that the example in this tutorial uses the proto3 version of the protocol buffers language, you can find out more in the [proto3 language guide][proto3]. @@ -47,12 +47,16 @@ Change your current directory to Tonic's repository root: $ cd tonic ``` -Tonic uses `rustfmt` to tidy up the code it generates, make sure it's installed. +Tonic uses `rustfmt` to tidy up the code it generates, so we'll make sure it's installed. ```shell $ rustup component add rustfmt ``` +**Note** Prior to rust's 1.39 release, Tonic may be pinned to a specific toolchain version. Running +the above command may first download and install a different toolchain. Check the project's [readme] +for the latest requirements. + Run the server ```shell $ cargo run --bin routeguide-server @@ -63,8 +67,6 @@ In a separate shell, run the client $ cargo run --bin routeguide-client ``` -**Note:** Prior to rust's 1.39 release, Tonic may be pinned to a specific toolchain version. -Consult the project's [readme] for the latest info. [readme]: https://github.com/hyperium/tonic#getting-started @@ -293,10 +295,12 @@ impl server::RouteGuide for RouteGuide { ``` **Note**: The `tonic::async_trait` attribute macro adds support for async functions in traits. It -uses [async-trait] internally. +uses [async-trait] internally. You can learn more about `async fn` in traits in the [async book]. + [cargo book]: https://doc.rust-lang.org/cargo/reference/environment-variables.html#environment-variables-cargo-sets-for-build-scripts [async-trait]: https://github.com/dtolnay/async-trait +[async book]: https://rust-lang.github.io/async-book/07_workarounds/06_async_in_traits.html ### Server state There are two pieces of state our service needs to access: an immutable list of features and a @@ -320,6 +324,8 @@ struct State { } ``` +**Note:** we are using `tokio::sync::Mutex` here, not `std::sync::Mutex`. + When our server boots, we are going to deserialize our features vector from a json file. Create the data file and a helper module to read and deserialize our features. @@ -332,7 +338,7 @@ You can find our example json data in [tonic-examples/data/route_guide_db.json][ the corresponding `data` module to load and deserialize it in [tonic-examples/routeguide/data.rs][data-module]. -**Note** If you are following along, you'll need to change the data file's path from +**Note:** If you are following along, you'll need to change the data file's path from `tonic-examples/data/route_guide_db.json` to `data/route_guide_db.json`. Next, we need to implement `Hash` and `Eq` for `Point`, so we can use point values as map keys: @@ -352,8 +358,9 @@ impl Eq for Point {} ``` -Lastly, we need two helper functions: `in_range` and `calc_distance`. We'll use them when performing -feature lookups. You can find them in [tonic-examples/src/routeguide/server.rs][in-range-fn]. +Lastly, we need implement two helper functions: `in_range` and `calc_distance`. We'll use them +when performing feature lookups. You can find them in +[tonic-examples/src/routeguide/server.rs][in-range-fn]. [route-guide-db]: https://github.com/hyperium/tonic/blob/master/tonic-examples/data/route_guide_db.json [data-module]: https://github.com/hyperium/tonic/blob/master/tonic-examples/src/routeguide/data.rs @@ -364,8 +371,8 @@ All our service methods receive a `tonic::Request` and return a `Result, tonic::Status>`. The concrete type of `T` depends on how our methods are declared in our *service* `.proto` definition. It can be either: -- A single value, e.g `Point`, `Vec` -- A stream of values, e.g. `impl Stream>` +- A single value, e.g `Point`, `Rectangle`, or even a message type that includes a repeated field. +- A stream of values, e.g. `impl Stream>`. #### Simple RPC Let's look at the simplest method first, `get_feature`, which just gets a `tonic::Request` @@ -469,7 +476,7 @@ async fn record_route( `record_route` is conceptually simple: we get a stream of `Points` and fold it into a `RouteSummary`. In other words, we build a summary value as we process each `Point` in our stream, one by one. When there are no more `Points` in our stream, we return the `RouteSummary` wrapped in a -`tonic::Response` +`tonic::Response`. #### Bidirectional streaming RPC Finally, let's look at our bidirectional streaming RPC `route_chat`, which receives a stream @@ -516,6 +523,9 @@ from one (input) stream to another (output) stream. As the input is processed, e inserted into the notes map, yielding a clone of the original `RouteNote`. The resulting stream is then returned to the caller. Neat. +**Note**: The funky `as` cast is needed due to a limitation in the rust compiler. This is expected +to be fixed soon. + [async-stream]: https://github.com/tokio-rs/async-stream ### Starting the server @@ -546,7 +556,7 @@ async fn main() -> Result<(), Box> { To handle requests, `Tonic` uses [Tower] and [hyper] internally. What this means, among other things, is that we have a flexible and composable stack we can build on top of. We can, for example, add an [interceptor][authentication-example] or implement [routing][router-example]. -In the future, Tonic will include higher level support for routing and interceptors. +In the future, Tonic may include higher level support for routing and interceptors. [Tower]: https://github.com/tower-rs From 460e82ae2efcc39278462775df090be7967806a7 Mon Sep 17 00:00:00 2001 From: Juan Alvarez Date: Sun, 6 Oct 2019 15:07:39 -0500 Subject: [PATCH 24/29] client tweaks --- tonic-examples/routeguide-tutorial.md | 67 +++++++++++++++++++------ tonic-examples/src/routeguide/client.rs | 2 +- 2 files changed, 53 insertions(+), 16 deletions(-) diff --git a/tonic-examples/routeguide-tutorial.md b/tonic-examples/routeguide-tutorial.md index 21e6a7ad9..1f7fc68e4 100644 --- a/tonic-examples/routeguide-tutorial.md +++ b/tonic-examples/routeguide-tutorial.md @@ -609,26 +609,32 @@ async fn main() -> Result<(), Box> { Same as in the server implementation, we start by bringing our generated code into scope. We then create a client in our main function, passing the server's full URL to `RouteGuideClient::connect`. -Our client is now ready to make service calls. Note that client is mutable, this is because it needs -to manage internal state. +Our client is now ready to make service calls. Note that `client` is mutable, this is because it +needs to manage internal state. [routeguide-client]: https://github.com/hyperium/tonic/blob/master/tonic-examples/src/routeguide/client.rs ### Calling service methods Now let's look at how we call our service methods. Note that in Tonic, RPCs are asynchronous, -which means that the RPC call needs to be `awaited`. +which means that RPC calls need to be `.await`ed. #### Simple RPC -Calling the simple RPC `get_feature` is as straightforward as calling a local method. +Calling the simple RPC `get_feature` is as straightforward as calling a local method: ```rust -client +use tonic::Request; +``` + +```rust +let response = client .get_feature(Request::new(Point { latitude: 409146138, longitude: -746188906, })) .await?; + +println!("RESPONSE = {:?}", response); ``` We call the `get_feature` client method, passing a single `Point` value wrapped in a `tonic::Request`. We get a `Result, tonic::Status>` back. @@ -637,6 +643,12 @@ We call the `get_feature` client method, passing a single `Point` value wrapped Here's where we call the server-side streaming method `list_features`, which returns a stream of geographical `Feature`s. +```rust +use futures::TryStreamExt; +use tonic::transport::Channel; +use std::error::Error; +``` + ```rust async fn print_features(client: &mut RouteGuideClient) -> Result<(), Box> { let rectangle = Rectangle { @@ -666,18 +678,24 @@ async fn print_features(client: &mut RouteGuideClient) -> Result<(), Bo As in the simple RPC, we pass a single value request. However, instead of getting a single value back, we get a stream of `Features`. -We use the `TryStreamExt`'s `try_next()` method to repeatedly read in the server's -responses to a response protocol buffer object (in this case a `Feature`) until there are no more -messages. +We use the the `try_next()` method from `futures::TryStreamExt` trait to repeatedly read in the +server's responses to a response protocol buffer object (in this case a `Feature`) until there are +no more messages left in the stream. #### Client-side streaming RPC The client-side streaming method `record_route` takes a stream of `Point`s and returns a single `RouteSummary` value. +```rust +use rand::rngs::ThreadRng; +use rand::Rng; +use futures::stream; +``` + ```rust async fn run_record_route(client: &mut RouteGuideClient) -> Result<(), Box> { let mut rng = rand::thread_rng(); - let point_count = rng.gen_range(2, 100); + let point_count: i32 = rng.gen_range(2, 100); let mut points = vec![]; for _ in 0..=point_count { @@ -695,16 +713,34 @@ async fn run_record_route(client: &mut RouteGuideClient) -> Result<(), Ok(()) } ``` + +```rust +fn random_point(rng: &mut ThreadRng) -> Point { + let latitude = (rng.gen_range(0, 180) - 90) * 10_000_000; + let longitude = (rng.gen_range(0, 360) - 180) * 10_000_000; + Point { + latitude, + longitude, + } +} +``` + We build a vector of a random number of `Result` values (between 2 and 100) and then convert -it into a `Stream` using the `futures::stream::iter` function. The resulting stream is then -wrapped in a `tonic::Request`. -We then match on the returned `Result`, printing the `RouteSummary` or an error. +it into a `Stream` using the `futures::stream::iter` function. This is a cheap an easy way to get +a stream suitable for passing into our service method. The resulting stream is then wrapped in a +`tonic::Request`. #### Bidirectional streaming RPC Finally, let's look at our bidirectional streaming RPC. The `route_chat` method takes a stream of `RouteNotes` and returns either another stream of `RouteNotes` or an error. + +```rust +use std::time::{Duration, Instant}; +use tokio::timer::Interval; +``` + ```rust async fn run_route_chat(client: &mut RouteGuideClient) -> Result<(), Box> { let start = Instant::now(); @@ -762,10 +798,11 @@ Tonic's default code generation configuration is convenient for self contained e projects. However, there are some cases when we need a slightly different workflow. For example: - When building rust clients and servers in different crates. -- When building a rust client or server (or both) as part of a larger, multi-language. -project. +- When building a rust client or server (or both) as part of a larger, multi-language project. +- When we want editor support for the generate code and our editor does not index the generated +files in the default location. -In general, whenever we want to keep our `.proto` definitions in a central place and generate +More generally, whenever we want to keep our `.proto` definitions in a central place and generate code for different crates or different languages, the default configuration is not enough. Luckily, `tonic_build` can be configured to fit whatever workflow we need. Here are just two diff --git a/tonic-examples/src/routeguide/client.rs b/tonic-examples/src/routeguide/client.rs index 3372e83bb..bc8ffb96f 100644 --- a/tonic-examples/src/routeguide/client.rs +++ b/tonic-examples/src/routeguide/client.rs @@ -40,7 +40,7 @@ async fn print_features(client: &mut RouteGuideClient) -> Result<(), Bo async fn run_record_route(client: &mut RouteGuideClient) -> Result<(), Box> { let mut rng = rand::thread_rng(); - let point_count = rng.gen_range(2, 100); + let point_count: i32 = rng.gen_range(2, 100); let mut points = vec![]; for _ in 0..=point_count { From e0d558b307716a8c1c0251c3ac47bfeabf0e9f92 Mon Sep 17 00:00:00 2001 From: Juan Alvarez Date: Sun, 6 Oct 2019 15:25:33 -0500 Subject: [PATCH 25/29] include server side `use` --- tonic-examples/routeguide-tutorial.md | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/tonic-examples/routeguide-tutorial.md b/tonic-examples/routeguide-tutorial.md index 1f7fc68e4..064260619 100644 --- a/tonic-examples/routeguide-tutorial.md +++ b/tonic-examples/routeguide-tutorial.md @@ -260,6 +260,13 @@ the package declared in in our `.proto` file, not a filename, e.g "routeguide.rs With this in place, we can stub out our service implementation: +```rust +use tonic::{Request, Response, Status}; +use tokio::sync::mpsc; +use std::pin::Pin; +use futures::Stream; +``` + ```rust #[tonic::async_trait] impl server::RouteGuide for RouteGuide { @@ -311,6 +318,12 @@ executor and that the `server::RouteGuide` trait has `Send + Sync + 'static` bou This in one way we can represent our state: +```rust +use tokio::sync::Mutex; +use std::sync::Arc; +use std::collections::HashMap; +``` + ```rust #[derive(Debug)] pub struct RouteGuide { @@ -343,6 +356,10 @@ the corresponding `data` module to load and deserialize it in Next, we need to implement `Hash` and `Eq` for `Point`, so we can use point values as map keys: +```rust +use std::hash::{Hasher, Hash}; +``` + ```rust impl Hash for Point { fn hash(&self, state: &mut H) @@ -438,6 +455,11 @@ Now let's look at something a little more complicated: the client-side streaming with information about their trip. As you can see, this time the method receives a `tonic::Request>`. +```rust +use std::time::Instant; +use futures::StreamExt; +``` + ```rust async fn record_route( &self, @@ -533,6 +555,11 @@ to be fixed soon. Once we've implemented all our methods, we also need to start up a gRPC server so that clients can actually use our service. This is how our `main` function looks like: +```rust +mod data; +use tonic::transport::Server; +``` + ```rust #[tokio::main] async fn main() -> Result<(), Box> { From f8a976b1e2c45c015f07694769ffd45cbf9baa61 Mon Sep 17 00:00:00 2001 From: Juan Alvarez Date: Tue, 8 Oct 2019 08:12:43 -0500 Subject: [PATCH 26/29] upgrade tonic to alpha.2 --- tonic-examples/routeguide-tutorial.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tonic-examples/routeguide-tutorial.md b/tonic-examples/routeguide-tutorial.md index 064260619..11cd906ea 100644 --- a/tonic-examples/routeguide-tutorial.md +++ b/tonic-examples/routeguide-tutorial.md @@ -183,10 +183,10 @@ serde_json = "1.0" prost = "0.5" rand = "0.7.2" tokio = "0.2.0-alpha.6" -tonic = "0.1.0-alpha.1" +tonic = "0.1.0-alpha.2" [build-dependencies] -tonic-build = "0.1.0-alpha.1" +tonic-build = "0.1.0-alpha.2" ``` Create a `build.rs` file at the root of your crate: From 193c35af66ad1b6946a400f47b4246b27a354b1e Mon Sep 17 00:00:00 2001 From: Juan Alvarez Date: Sun, 13 Oct 2019 11:48:52 -0500 Subject: [PATCH 27/29] move notes map out of shared state --- tonic-examples/routeguide-tutorial.md | 55 +++++++------------------ tonic-examples/src/routeguide/server.rs | 32 +++++--------- 2 files changed, 25 insertions(+), 62 deletions(-) diff --git a/tonic-examples/routeguide-tutorial.md b/tonic-examples/routeguide-tutorial.md index 11cd906ea..0a76403c3 100644 --- a/tonic-examples/routeguide-tutorial.md +++ b/tonic-examples/routeguide-tutorial.md @@ -183,10 +183,10 @@ serde_json = "1.0" prost = "0.5" rand = "0.7.2" tokio = "0.2.0-alpha.6" -tonic = "0.1.0-alpha.2" +tonic = "0.1.0-alpha.3" [build-dependencies] -tonic-build = "0.1.0-alpha.2" +tonic-build = "0.1.0-alpha.3" ``` Create a `build.rs` file at the root of your crate: @@ -310,37 +310,17 @@ uses [async-trait] internally. You can learn more about `async fn` in traits in [async book]: https://rust-lang.github.io/async-book/07_workarounds/06_async_in_traits.html ### Server state -There are two pieces of state our service needs to access: an immutable list of features and a -mutable map from points to route notes. - -When designing our state shape, we must consider that our server will run in a multi-threaded Tokio -executor and that the `server::RouteGuide` trait has `Send + Sync + 'static` bounds. - -This in one way we can represent our state: - -```rust -use tokio::sync::Mutex; -use std::sync::Arc; -use std::collections::HashMap; -``` +Our service needs access to an immutable list of features. When the server starts, we are going to +deserialize them from a json file and keep them around as our only piece of shared state: ```rust #[derive(Debug)] pub struct RouteGuide { - state: State, -} - -#[derive(Debug, Clone)] -struct State { features: Arc>, - notes: Arc>>>, } ``` -**Note:** we are using `tokio::sync::Mutex` here, not `std::sync::Mutex`. - -When our server boots, we are going to deserialize our features vector from a json file. -Create the data file and a helper module to read and deserialize our features. +Create the json data file and a helper module to read and deserialize our features. ```shell $ mkdir data && touch data/route_guide_db.json @@ -398,7 +378,7 @@ an empty one. ```rust async fn get_feature(&self, request: Request) -> Result, Status> { - for feature in &self.state.features[..] { + for feature in &self.features[..] { if feature.location.as_ref() == Some(request.get_ref()) { return Ok(Response::new(feature.clone())); } @@ -426,11 +406,10 @@ async fn list_features( request: Request, ) -> Result, Status> { let (mut tx, rx) = mpsc::channel(4); - - let state = self.state.clone(); + let features = self.features.clone(); tokio::spawn(async move { - for feature in &state.features[..] { + for feature in &features[..] { if in_range(feature.location.as_ref().unwrap(), request.get_ref()) { tx.send(Ok(feature.clone())).await.unwrap(); } @@ -476,7 +455,7 @@ async fn record_route( let point = point?; summary.point_count += 1; - for feature in &self.state.features[..] { + for feature in &self.features[..] { if feature.location.as_ref() == Some(&point) { summary.feature_count += 1; } @@ -511,9 +490,9 @@ async fn route_chat( &self, request: Request>, ) -> Result, Status> { + let mut notes = HashMap::new(); let stream = request.into_inner(); - let mut state = self.state.clone(); - + let output = async_stream::try_stream! { futures::pin_mut!(stream); @@ -522,11 +501,10 @@ async fn route_chat( let location = note.location.clone().unwrap(); - let mut notes = state.notes.lock().await; - let notes = notes.entry(location).or_insert(vec![]); - notes.push(note); + let location_notes = notes.entry(location).or_insert(vec![]); + location_notes.push(note); - for note in notes { + for note in location_notes { yield note.clone(); } } @@ -566,10 +544,7 @@ async fn main() -> Result<(), Box> { let addr = "[::1]:10000".parse().unwrap(); let route_guide = RouteGuide { - state: State { - features: Arc::new(data::load()), - notes: Arc::new(Mutex::new(HashMap::new())), - }, + features: Arc::new(data::load()), }; let svc = server::RouteGuideServer::new(route_guide); diff --git a/tonic-examples/src/routeguide/server.rs b/tonic-examples/src/routeguide/server.rs index 4c565623d..5732c52cc 100644 --- a/tonic-examples/src/routeguide/server.rs +++ b/tonic-examples/src/routeguide/server.rs @@ -6,7 +6,7 @@ use std::hash::{Hash, Hasher}; use std::pin::Pin; use std::sync::Arc; use std::time::Instant; -use tokio::sync::{mpsc, Mutex}; +use tokio::sync::mpsc; use tonic::transport::Server; use tonic::{Request, Response, Status}; @@ -18,13 +18,7 @@ use routeguide::{server, Feature, Point, Rectangle, RouteNote, RouteSummary}; #[derive(Debug)] pub struct RouteGuide { - state: State, -} - -#[derive(Debug, Clone)] -struct State { features: Arc>, - notes: Arc>>>, } #[tonic::async_trait] @@ -32,7 +26,7 @@ impl server::RouteGuide for RouteGuide { async fn get_feature(&self, request: Request) -> Result, Status> { println!("GetFeature = {:?}", request); - for feature in &self.state.features[..] { + for feature in &self.features[..] { if feature.location.as_ref() == Some(request.get_ref()) { return Ok(Response::new(feature.clone())); } @@ -55,11 +49,10 @@ impl server::RouteGuide for RouteGuide { println!("ListFeatures = {:?}", request); let (mut tx, rx) = mpsc::channel(4); - - let state = self.state.clone(); + let features = self.features.clone(); tokio::spawn(async move { - for feature in &state.features[..] { + for feature in &features[..] { if in_range(feature.location.as_ref().unwrap(), request.get_ref()) { println!(" => send {:?}", feature); tx.send(Ok(feature.clone())).await.unwrap(); @@ -96,7 +89,7 @@ impl server::RouteGuide for RouteGuide { summary.point_count += 1; // Find features - for feature in &self.state.features[..] { + for feature in &self.features[..] { if feature.location.as_ref() == Some(&point) { summary.feature_count += 1; } @@ -123,8 +116,8 @@ impl server::RouteGuide for RouteGuide { ) -> Result, Status> { println!("RouteChat"); + let mut notes = HashMap::new(); let stream = request.into_inner(); - let state = self.state.clone(); let output = async_stream::try_stream! { futures::pin_mut!(stream); @@ -134,11 +127,10 @@ impl server::RouteGuide for RouteGuide { let location = note.location.clone().unwrap(); - let mut notes = state.notes.lock().await; - let notes = notes.entry(location).or_insert(vec![]); - notes.push(note); + let location_notes = notes.entry(location).or_insert(vec![]); + location_notes.push(note); - for note in notes { + for note in location_notes { yield note.clone(); } } @@ -158,11 +150,7 @@ async fn main() -> Result<(), Box> { println!("Listening on: {}", addr); let route_guide = RouteGuide { - state: State { - // Load data file - features: Arc::new(data::load()), - notes: Arc::new(Mutex::new(HashMap::new())), - }, + features: Arc::new(data::load()), }; let svc = server::RouteGuideServer::new(route_guide); From ddf33dad4552e348c6386f8ac507b54b38e13d18 Mon Sep 17 00:00:00 2001 From: Juan Alvarez Date: Mon, 14 Oct 2019 15:29:19 -0500 Subject: [PATCH 28/29] address blgBV's comments --- tonic-examples/routeguide-tutorial.md | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/tonic-examples/routeguide-tutorial.md b/tonic-examples/routeguide-tutorial.md index 0a76403c3..849e2b108 100644 --- a/tonic-examples/routeguide-tutorial.md +++ b/tonic-examples/routeguide-tutorial.md @@ -67,6 +67,15 @@ In a separate shell, run the client $ cargo run --bin routeguide-client ``` +You should see some logging output flying past really quickly on both terminal windows. On the +shell where you ran the client binary, you should see the output of the bidirectional streaming rpc, +printing 1 line per second: + + NOTE = RouteNote { location: Some(Point { latitude: 409146139, longitude: -746188906 }), message: "at 1.000319208s" } + +If you scroll up you should see the output of the other 3 request types: simple rpc, server-side +streaming and client-side streaming. + [readme]: https://github.com/hyperium/tonic#getting-started @@ -208,8 +217,8 @@ That's it. The generated code contains: - A service trait we'll need to implement: `server::RouteGuide`. - A client type we'll use to call the server: `client::RouteGuideClient`. -If your are curious as to where the generated files are, keep reading. The mystery will be revealed. -We can now move on to the fun part. +If your are curious as to where the generated files are, keep reading. The mystery will be revealed +soon! We can now move on to the fun part. [PROST!]: https://github.com/danburkert/prost From bf7428c561cbad8929e7630dc3f9ee9d78d352b1 Mon Sep 17 00:00:00 2001 From: Lucio Franco Date: Fri, 18 Oct 2019 17:52:31 -0400 Subject: [PATCH 29/29] Update route guide tutorial to track latest master --- tonic-examples/routeguide-tutorial.md | 104 ++++++++++++------------ tonic-examples/src/routeguide/client.rs | 6 +- 2 files changed, 55 insertions(+), 55 deletions(-) diff --git a/tonic-examples/routeguide-tutorial.md b/tonic-examples/routeguide-tutorial.md index 849e2b108..0763608a2 100644 --- a/tonic-examples/routeguide-tutorial.md +++ b/tonic-examples/routeguide-tutorial.md @@ -8,11 +8,11 @@ and Tonic. By walking through this example you'll learn how to: - Write a simple client and server for your service. It assumes you are familiar with [protocol buffers] and basic Rust. Note that the example in -this tutorial uses the proto3 version of the protocol buffers language, you can find out more in the -[proto3 language guide][proto3]. +this tutorial uses the proto3 version of the protocol buffers language, you can find out more in the +[proto3 language guide][proto3]. [grpc-go]: https://github.com/grpc/grpc-go/blob/master/examples/gotutorial.md -[protocol buffers]: https://developers.google.com/protocol-buffers/docs/overview +[protocol buffers]: https://developers.google.com/protocol-buffers/docs/overview [proto3]: https://developers.google.com/protocol-buffers/docs/proto3 ## Why use gRPC? @@ -21,7 +21,7 @@ Our example is a simple route mapping application that lets clients get informat on their route, create a summary of their route, and exchange route information such as traffic updates with the server and other clients. -With gRPC we can define our service once in a `.proto` file and implement clients and servers in +With gRPC we can define our service once in a `.proto` file and implement clients and servers in any of gRPC's supported languages, which in turn can be run in environments ranging from servers inside Google to your own tablet - all the complexity of communication between different languages and environments is handled for you by gRPC. We also get all the advantages of working with @@ -29,16 +29,16 @@ protocol buffers, including efficient serialization, a simple IDL, and easy inte ## Prerequisites -To run the sample code and walk through the tutorial, the only prerequisite is Rust itself. +To run the sample code and walk through the tutorial, the only prerequisite is Rust itself. [rustup] is a convenient tool to install it, if you haven't already. [rustup]: https://rustup.rs - + ## Running the example Clone or download Tonic's repository: -```shell +```shell $ git clone https://github.com/hyperium/tonic.git ``` @@ -69,10 +69,10 @@ $ cargo run --bin routeguide-client You should see some logging output flying past really quickly on both terminal windows. On the shell where you ran the client binary, you should see the output of the bidirectional streaming rpc, -printing 1 line per second: +printing 1 line per second: NOTE = RouteNote { location: Some(Point { latitude: 409146139, longitude: -746188906 }), message: "at 1.000319208s" } - + If you scroll up you should see the output of the other 3 request types: simple rpc, server-side streaming and client-side streaming. @@ -82,7 +82,7 @@ streaming and client-side streaming. ## Project setup We will develop our example from scratch in a new crate: - + ```shell $ cargo new routeguide $ cd routeguide @@ -91,7 +91,7 @@ $ cd routeguide ## Defining the service -Our first step is to define the gRPC *service* and the method *request* and *response* types using +Our first step is to define the gRPC *service* and the method *request* and *response* types using [protocol buffers]. We will keep our `.proto` files in a directory in our crate's root. Note that Tonic does not really care where our `.proto` definitions live. We will see how to use different [code generation configuration](#tonic-build) later in the tutorial. @@ -115,16 +115,16 @@ Then you define `rpc` methods inside your service definition, specifying their r types. gRPC lets you define four kinds of service method, all of which are used in the `RouteGuide` service: -- A *simple RPC* where the client sends a request to the server and waits for a response to come +- A *simple RPC* where the client sends a request to the server and waits for a response to come back, just like a normal function call. ```proto // Obtains the feature at a given position. rpc GetFeature(Point) returns (Feature) {} ``` -- A *server-side streaming RPC* where the client sends a request to the server and gets a stream -to read a sequence of messages back. The client reads from the returned stream until there are -no more messages. As you can see in our example, you specify a server-side streaming method by +- A *server-side streaming RPC* where the client sends a request to the server and gets a stream +to read a sequence of messages back. The client reads from the returned stream until there are +no more messages. As you can see in our example, you specify a server-side streaming method by placing the `stream` keyword before the *response* type. ```proto // Obtains the Features available within the given Rectangle. Results are @@ -134,7 +134,7 @@ placing the `stream` keyword before the *response* type. rpc ListFeatures(Rectangle) returns (stream Feature) {} ``` -- A *client-side streaming RPC* where the client writes a sequence of messages and sends them to +- A *client-side streaming RPC* where the client writes a sequence of messages and sends them to the server. Once the client has finished writing the messages, it waits for the server to read them all and return its response. You specify a client-side streaming method by placing the `stream` keyword before the *request* type. @@ -146,7 +146,7 @@ keyword before the *request* type. - A *bidirectional streaming RPC* where both sides send a sequence of messages. The two streams operate independently, so clients and servers can read and write in whatever -order they like: for example, the server could wait to receive all the client messages before +order they like: for example, the server could wait to receive all the client messages before writing its responses, or it could alternately read a message then write a message, or some other combination of reads and writes. The order of messages in each stream is preserved. You specify this type of method by placing the `stream` keyword before both the request and the response. @@ -156,7 +156,7 @@ this type of method by placing the `stream` keyword before both the request and rpc RouteChat(stream RouteNote) returns (stream RouteNote) {} ``` -Our `.proto` file also contains protocol buffer message type definitions for all the request and +Our `.proto` file also contains protocol buffer message type definitions for all the request and response types used in our service methods - for example, here's the `Point` message type: ```proto // Points are represented as latitude-longitude pairs in the E7 representation @@ -225,7 +225,7 @@ soon! We can now move on to the fun part. ## Creating the server First let's look at how we create a `RouteGuide` server. If you're only interested in creating gRPC -clients, you can skip this section and go straight to [Creating the client](#client) +clients, you can skip this section and go straight to [Creating the client](#client) (though you might find it interesting anyway!). There are two parts to making our `RouteGuide` service do its job: @@ -233,7 +233,7 @@ There are two parts to making our `RouteGuide` service do its job: - Implementing the service trait generated from our service definition. - Running a gRPC server to listen for requests from clients. -You can find our example `RouteGuide` server in +You can find our example `RouteGuide` server in [tonic-examples/src/routeguide/server.rs][routeguide-server]. [routeguide-server]: https://github.com/hyperium/tonic/blob/master/tonic-examples/src/routeguide/server.rs @@ -291,7 +291,7 @@ impl server::RouteGuide for RouteGuide { ) -> Result, Status> { unimplemented!() } - + async fn record_route( &self, _request: Request>, @@ -311,7 +311,7 @@ impl server::RouteGuide for RouteGuide { ``` **Note**: The `tonic::async_trait` attribute macro adds support for async functions in traits. It -uses [async-trait] internally. You can learn more about `async fn` in traits in the [async book]. +uses [async-trait] internally. You can learn more about `async fn` in traits in the [async book]. [cargo book]: https://doc.rust-lang.org/cargo/reference/environment-variables.html#environment-variables-cargo-sets-for-build-scripts @@ -381,7 +381,7 @@ are declared in our *service* `.proto` definition. It can be either: - A stream of values, e.g. `impl Stream>`. #### Simple RPC -Let's look at the simplest method first, `get_feature`, which just gets a `tonic::Request` +Let's look at the simplest method first, `get_feature`, which just gets a `tonic::Request` from the client and tries to find a feature at the given `Point`. If no feature is found, it returns an empty one. @@ -432,16 +432,16 @@ async fn list_features( Like `get_feature`, `list_features`'s input is a single message, a `Rectangle` in this case. This time, however, we need to return a stream of values, rather than a single one. We create a channel and spawn a new asynchronous task where we perform a lookup, sending -the features that satisfy our constraints into the channel. +the features that satisfy our constraints into the channel. The `Stream` half of the channel is returned to the caller, wrapped in a `tonic::Response`. #### Client-side streaming RPC -Now let's look at something a little more complicated: the client-side streaming method -`record_route`, where we get a stream of `Point`s from the client and return a single `RouteSummary` -with information about their trip. As you can see, this time the method receives a -`tonic::Request>`. +Now let's look at something a little more complicated: the client-side streaming method +`record_route`, where we get a stream of `Point`s from the client and return a single `RouteSummary` +with information about their trip. As you can see, this time the method receives a +`tonic::Request>`. ```rust use std::time::Instant; @@ -485,7 +485,7 @@ async fn record_route( `record_route` is conceptually simple: we get a stream of `Points` and fold it into a `RouteSummary`. In other words, we build a summary value as we process each `Point` in our stream, one by one. -When there are no more `Points` in our stream, we return the `RouteSummary` wrapped in a +When there are no more `Points` in our stream, we return the `RouteSummary` wrapped in a `tonic::Response`. #### Bidirectional streaming RPC @@ -501,7 +501,7 @@ async fn route_chat( ) -> Result, Status> { let mut notes = HashMap::new(); let stream = request.into_inner(); - + let output = async_stream::try_stream! { futures::pin_mut!(stream); @@ -523,7 +523,7 @@ async fn route_chat( as Pin< Box> + Send + 'static>, >)) - + } ``` @@ -601,7 +601,7 @@ $ mv src/main.rs src/server.rs $ touch src/client.rs ``` -To call service methods, we first need to create a gRPC *client* to communicate with the server. +To call service methods, we first need to create a gRPC *client* to communicate with the server. ```rust pub mod route_guide { @@ -613,7 +613,7 @@ use route_guide::{client::RouteGuideClient, Point, Rectangle, RouteNote}; #[tokio::main] async fn main() -> Result<(), Box> { let mut client = RouteGuideClient::connect("http://[::1]:10000")?; - + Ok(()) } ``` @@ -627,7 +627,7 @@ needs to manage internal state. ### Calling service methods -Now let's look at how we call our service methods. Note that in Tonic, RPCs are asynchronous, +Now let's look at how we call our service methods. Note that in Tonic, RPCs are asynchronous, which means that RPC calls need to be `.await`ed. #### Simple RPC @@ -644,15 +644,15 @@ let response = client longitude: -746188906, })) .await?; - + println!("RESPONSE = {:?}", response); ``` We call the `get_feature` client method, passing a single `Point` value wrapped in a `tonic::Request`. We get a `Result, tonic::Status>` back. #### Server-side streaming RPC -Here's where we call the server-side streaming method `list_features`, which returns a stream of -geographical `Feature`s. +Here's where we call the server-side streaming method `list_features`, which returns a stream of +geographical `Feature`s. ```rust use futures::TryStreamExt; @@ -672,13 +672,13 @@ async fn print_features(client: &mut RouteGuideClient) -> Result<(), Bo longitude: -730000000, }), }; - + let mut stream = client .list_features(Request::new(rectangle)) .await? .into_inner(); - while let Some(feature) = stream.try_next().await? { + while let Some(feature) = stream.message().await? { println!("NOTE = {:?}", feature); } @@ -686,16 +686,16 @@ async fn print_features(client: &mut RouteGuideClient) -> Result<(), Bo } ``` -As in the simple RPC, we pass a single value request. However, instead of getting a -single value back, we get a stream of `Features`. +As in the simple RPC, we pass a single value request. However, instead of getting a +single value back, we get a stream of `Features`. -We use the the `try_next()` method from `futures::TryStreamExt` trait to repeatedly read in the +We use the the `message()` method from the `tonic::Streaming` struct to repeatedly read in the server's responses to a response protocol buffer object (in this case a `Feature`) until there are no more messages left in the stream. #### Client-side streaming RPC The client-side streaming method `record_route` takes a stream of `Point`s and returns a single -`RouteSummary` value. +`RouteSummary` value. ```rust use rand::rngs::ThreadRng; @@ -710,7 +710,7 @@ async fn run_record_route(client: &mut RouteGuideClient) -> Result<(), let mut points = vec![]; for _ in 0..=point_count { - points.push(Ok(random_point(&mut rng))) + points.push(random_point(&mut rng)) } println!("Traversing {} points", points.len()); @@ -736,7 +736,7 @@ fn random_point(rng: &mut ThreadRng) -> Point { } ``` -We build a vector of a random number of `Result` values (between 2 and 100) and then convert +We build a vector of a random number of `Point` values (between 2 and 100) and then convert it into a `Stream` using the `futures::stream::iter` function. This is a cheap an easy way to get a stream suitable for passing into our service method. The resulting stream is then wrapped in a `tonic::Request`. @@ -756,7 +756,7 @@ use tokio::timer::Interval; async fn run_route_chat(client: &mut RouteGuideClient) -> Result<(), Box> { let start = Instant::now(); - let outbound = async_stream::try_stream! { + let outbound = async_stream::stream! { let mut interval = Interval::new_interval(Duration::from_secs(1)); while let Some(time) = interval.next().await { @@ -777,14 +777,14 @@ async fn run_route_chat(client: &mut RouteGuideClient) -> Result<(), Bo let response = client.route_chat(request).await?; let mut inbound = response.into_inner(); - while let Some(note) = inbound.try_next().await? { + while let Some(note) = inbound.message().await? { println!("NOTE = {:?}", note); } Ok(()) } ``` -In this case, we use the [async-stream] crate to generate our outbound stream, yielding +In this case, we use the [async-stream] crate to generate our outbound stream, yielding `RouteNote` values in one second intervals. We then iterate over the stream returned by the server, printing each value in the stream. @@ -820,7 +820,7 @@ Luckily, `tonic_build` can be configured to fit whatever workflow we need. Here possibilities: 1) We can keep our `.proto` definitions in a separate crate and generate our code on demand, as -opposed to at build time, placing the resulting modules wherever we need them. +opposed to at build time, placing the resulting modules wherever we need them. `main.rs` @@ -832,11 +832,11 @@ fn main() { .compile(&["path/my_proto.proto"], &["path"]) .expect("failed to compile protos"); } -``` +``` On `cargo run`, this will generate code for the client only, and place the resulting file in `another_crate/src/pb`. 2) Similarly, we could also keep the `.proto` definitions in a separate crate and then use that -crate as a direct dependency wherever we need it. - +crate as a direct dependency wherever we need it. + diff --git a/tonic-examples/src/routeguide/client.rs b/tonic-examples/src/routeguide/client.rs index 214742b12..e382c78e3 100644 --- a/tonic-examples/src/routeguide/client.rs +++ b/tonic-examples/src/routeguide/client.rs @@ -1,4 +1,4 @@ -use futures::{stream, TryStreamExt}; +use futures::stream; use rand::rngs::ThreadRng; use rand::Rng; use route_guide::{Point, Rectangle, RouteNote}; @@ -31,7 +31,7 @@ async fn print_features(client: &mut RouteGuideClient) -> Result<(), Bo .await? .into_inner(); - while let Some(feature) = stream.try_next().await? { + while let Some(feature) = stream.message().await? { println!("NOTE = {:?}", feature); } @@ -82,7 +82,7 @@ async fn run_route_chat(client: &mut RouteGuideClient) -> Result<(), Bo let response = client.route_chat(request).await?; let mut inbound = response.into_inner(); - while let Some(note) = inbound.try_next().await? { + while let Some(note) = inbound.message().await? { println!("NOTE = {:?}", note); }