1# gRPC Basics: Tonic 2 3This tutorial, adapted from [grpc-go], provides a basic introduction to working with gRPC 4and Tonic. By walking through this example you'll learn how to: 5 6- Define a service in a `.proto` file. 7- Generate server and client code. 8- Write a simple client and server for your service. 9 10It assumes you are familiar with [protocol buffers] and basic Rust. Note that the example in 11this tutorial uses the proto3 version of the protocol buffers language, you can find out more in the 12[proto3 language guide][proto3]. 13 14[grpc-go]: https://github.com/grpc/grpc-go/blob/master/examples/gotutorial.md 15[protocol buffers]: https://developers.google.com/protocol-buffers/docs/overview 16[proto3]: https://developers.google.com/protocol-buffers/docs/proto3 17 18## Why use gRPC? 19 20Our example is a simple route mapping application that lets clients get information about features 21on their route, create a summary of their route, and exchange route information such as traffic 22updates with the server and other clients. 23 24With gRPC we can define our service once in a `.proto` file and implement clients and servers in 25any of gRPC's supported languages, which in turn can be run in environments ranging from servers 26inside Google to your own tablet - all the complexity of communication between different languages 27and environments is handled for you by gRPC. We also get all the advantages of working with 28protocol buffers, including efficient serialization, a simple IDL, and easy interface updating. 29 30## Prerequisites 31 32To run the sample code and walk through the tutorial, the only prerequisite is Rust itself. 33[rustup] is a convenient tool to install it, if you haven't already. 34 35[rustup]: https://rustup.rs 36 37## Running the example 38 39Clone or download Tonic's repository: 40 41```shell 42$ git clone https://github.com/hyperium/tonic.git 43``` 44 45Change your current directory to Tonic's repository root: 46```shell 47$ cd tonic 48``` 49 50Tonic uses `rustfmt` to tidy up the code it generates, so we'll make sure it's installed. 51 52```shell 53$ rustup component add rustfmt 54``` 55 56Run the server 57```shell 58$ cargo run --bin routeguide-server 59``` 60 61In a separate shell, run the client 62```shell 63$ cargo run --bin routeguide-client 64``` 65 66You should see some logging output flying past really quickly on both terminal windows. On the 67shell where you ran the client binary, you should see the output of the bidirectional streaming rpc, 68printing 1 line per second: 69 70 NOTE = RouteNote { location: Some(Point { latitude: 409146139, longitude: -746188906 }), message: "at 1.000319208s" } 71 72If you scroll up you should see the output of the other 3 request types: simple rpc, server-side 73streaming and client-side streaming. 74 75 76[readme]: https://github.com/hyperium/tonic#getting-started 77 78## Project setup 79 80We will develop our example from scratch in a new crate: 81 82```shell 83$ cargo new routeguide 84$ cd routeguide 85``` 86 87 88## Defining the service 89 90Our first step is to define the gRPC *service* and the method *request* and *response* types using 91[protocol buffers]. We will keep our `.proto` files in a directory in our crate's root. 92Note that Tonic does not really care where our `.proto` definitions live. We will see how to use 93different [code generation configuration](#tonic-build) later in the tutorial. 94 95```shell 96$ mkdir proto && touch proto/route_guide.proto 97``` 98 99You can see the complete `.proto` file in 100[tonic-examples/proto/routeguide/route_guide.proto][routeguide-proto]. 101 102To define a service, you specify a named `service` in your `.proto` file: 103 104```proto 105service RouteGuide { 106 ... 107} 108``` 109 110Then you define `rpc` methods inside your service definition, specifying their request and response 111types. gRPC lets you define four kinds of service method, all of which are used in the `RouteGuide` 112service: 113 114- A *simple RPC* where the client sends a request to the server and waits for a response to come 115back, just like a normal function call. 116```proto 117 // Obtains the feature at a given position. 118 rpc GetFeature(Point) returns (Feature) {} 119``` 120 121- A *server-side streaming RPC* where the client sends a request to the server and gets a stream 122to read a sequence of messages back. The client reads from the returned stream until there are 123no more messages. As you can see in our example, you specify a server-side streaming method by 124placing the `stream` keyword before the *response* type. 125```proto 126 // Obtains the Features available within the given Rectangle. Results are 127 // streamed rather than returned at once (e.g. in a response message with a 128 // repeated field), as the rectangle may cover a large area and contain a 129 // huge number of features. 130 rpc ListFeatures(Rectangle) returns (stream Feature) {} 131``` 132 133- A *client-side streaming RPC* where the client writes a sequence of messages and sends them to 134the server. Once the client has finished writing the messages, it waits for the server to read them 135all and return its response. You specify a client-side streaming method by placing the `stream` 136keyword before the *request* type. 137```proto 138 // Accepts a stream of Points on a route being traversed, returning a 139 // RouteSummary when traversal is completed. 140 rpc RecordRoute(stream Point) returns (RouteSummary) {} 141``` 142 143- A *bidirectional streaming RPC* where both sides send a sequence of messages. The two streams 144operate independently, so clients and servers can read and write in whatever 145order they like: for example, the server could wait to receive all the client messages before 146writing its responses, or it could alternately read a message then write a message, or some other 147combination of reads and writes. The order of messages in each stream is preserved. You specify 148this type of method by placing the `stream` keyword before both the request and the response. 149```proto 150 // Accepts a stream of RouteNotes sent while a route is being traversed, 151 // while receiving other RouteNotes (e.g. from other users). 152 rpc RouteChat(stream RouteNote) returns (stream RouteNote) {} 153``` 154 155Our `.proto` file also contains protocol buffer message type definitions for all the request and 156response types used in our service methods - for example, here's the `Point` message type: 157```proto 158// Points are represented as latitude-longitude pairs in the E7 representation 159// (degrees multiplied by 10**7 and rounded to the nearest integer). 160// Latitudes should be in the range +/- 90 degrees and longitude should be in 161// the range +/- 180 degrees (inclusive). 162message Point { 163 int32 latitude = 1; 164 int32 longitude = 2; 165} 166``` 167 168[routeguide-proto]: https://github.com/hyperium/tonic/blob/master/tonic-examples/proto/routeguide/route_guide.proto 169 170## Generating client and server code 171 172Tonic can be configured to generate code as part cargo's normal build process. This is very 173convenient because once we've set everything up, there is no extra step to keep the generated code 174and our `.proto` definitions in sync. 175 176Behind the scenes, Tonic uses [PROST!] to handle protocol buffer serialization and code 177generation. 178 179Edit `Cargo.toml` and add all the dependencies we'll need for this example: 180 181```toml 182[dependencies] 183async-stream = "0.1.2" 184bytes = "0.4" 185futures-preview = { version = "0.3.0-alpha.19", default-features = false, features = ["alloc"]} 186serde = { version = "1.0", features = ["derive"] } 187serde_json = "1.0" 188prost = "0.5" 189rand = "0.7.2" 190tokio = "0.2.0-alpha.6" 191tonic = "0.1.0-alpha.5" 192 193[build-dependencies] 194tonic-build = "0.1.0-alpha.5" 195``` 196 197Create a `build.rs` file at the root of your crate: 198 199```rust 200fn main() { 201 tonic_build::compile_protos("proto/route_guide.proto") 202 .unwrap_or_else(|e| panic!("Failed to compile protos {:?}", e)); 203} 204``` 205 206```shell 207$ cargo build 208``` 209 210That's it. The generated code contains: 211 212- Struct definitions for message types `Point`, `Rectangle`, `Feature`, `RouteNote`, `RouteSummary`. 213- A service trait we'll need to implement: `server::RouteGuide`. 214- A client type we'll use to call the server: `client::RouteGuideClient<T>`. 215 216If your are curious as to where the generated files are, keep reading. The mystery will be revealed 217soon! We can now move on to the fun part. 218 219[PROST!]: https://github.com/danburkert/prost 220 221## Creating the server 222 223First let's look at how we create a `RouteGuide` server. If you're only interested in creating gRPC 224clients, you can skip this section and go straight to [Creating the client](#client) 225(though you might find it interesting anyway!). 226 227There are two parts to making our `RouteGuide` service do its job: 228 229- Implementing the service trait generated from our service definition. 230- Running a gRPC server to listen for requests from clients. 231 232You can find our example `RouteGuide` server in 233[tonic-examples/src/routeguide/server.rs][routeguide-server]. 234 235[routeguide-server]: https://github.com/hyperium/tonic/blob/master/tonic-examples/src/routeguide/server.rs 236 237### Implementing the server::RouteGuide trait 238 239We can start by defining a struct to represent our service, we can do this on `main.rs` for now: 240 241```rust 242#[derive(Debug)] 243struct RouteGuide; 244``` 245 246Next, we need to implement the `server::RouteGuide` trait that is generated in our build step. 247The generated code is placed inside our target directory, in a location defined by the `OUT_DIR` 248environment variable that is set by cargo. For our example, this means you can find the generated 249code in a path similar to `target/debug/build/routeguide/out/routeguide.rs`. 250 251You can learn more about `build.rs` and the `OUT_DIR` environment variable in the [cargo book]. 252 253We can use Tonic's `include_proto` macro to bring the generated code into scope: 254 255```rust 256pub mod routeguide { 257 tonic::include_proto!("routeguide"); 258} 259 260use routeguide::{server, Feature, Point, Rectangle, RouteNote, RouteSummary}; 261``` 262 263**Note**: The token passed to the `include_proto` macro (in our case "routeguide") is the name of 264the package declared in in our `.proto` file, not a filename, e.g "routeguide.rs". 265 266With this in place, we can stub out our service implementation: 267 268```rust 269use tonic::{Request, Response, Status}; 270use tokio::sync::mpsc; 271use std::pin::Pin; 272use futures::Stream; 273``` 274 275```rust 276#[tonic::async_trait] 277impl server::RouteGuide for RouteGuide { 278 async fn get_feature(&self, _request: Request<Point>) -> Result<Response<Feature>, Status> { 279 unimplemented!() 280 } 281 282 type ListFeaturesStream = mpsc::Receiver<Result<Feature, Status>>; 283 284 async fn list_features( 285 &self, 286 _request: Request<Rectangle>, 287 ) -> Result<Response<Self::ListFeaturesStream>, Status> { 288 unimplemented!() 289 } 290 291 async fn record_route( 292 &self, 293 _request: Request<tonic::Streaming<Point>>, 294 ) -> Result<Response<RouteSummary>, Status> { 295 unimplemented!() 296 } 297 298 type RouteChatStream = Pin<Box<dyn Stream<Item = Result<RouteNote, Status>> + Send + Sync + 'static>>; 299 300 async fn route_chat( 301 &self, 302 _request: Request<tonic::Streaming<RouteNote>>, 303 ) -> Result<Response<Self::RouteChatStream>, Status> { 304 unimplemented!() 305 } 306} 307``` 308 309**Note**: The `tonic::async_trait` attribute macro adds support for async functions in traits. It 310uses [async-trait] internally. You can learn more about `async fn` in traits in the [async book]. 311 312 313[cargo book]: https://doc.rust-lang.org/cargo/reference/environment-variables.html#environment-variables-cargo-sets-for-build-scripts 314[async-trait]: https://github.com/dtolnay/async-trait 315[async book]: https://rust-lang.github.io/async-book/07_workarounds/06_async_in_traits.html 316 317### Server state 318Our service needs access to an immutable list of features. When the server starts, we are going to 319deserialize them from a json file and keep them around as our only piece of shared state: 320 321```rust 322#[derive(Debug)] 323pub struct RouteGuide { 324 features: Arc<Vec<Feature>>, 325} 326``` 327 328Create the json data file and a helper module to read and deserialize our features. 329 330```shell 331$ mkdir data && touch data/route_guide_db.json 332$ touch src/data.rs 333``` 334 335You can find our example json data in [tonic-examples/data/route_guide_db.json][route-guide-db] and 336the corresponding `data` module to load and deserialize it in 337[tonic-examples/routeguide/data.rs][data-module]. 338 339**Note:** If you are following along, you'll need to change the data file's path from 340`tonic-examples/data/route_guide_db.json` to `data/route_guide_db.json`. 341 342Next, we need to implement `Hash` and `Eq` for `Point`, so we can use point values as map keys: 343 344```rust 345use std::hash::{Hasher, Hash}; 346``` 347 348```rust 349impl Hash for Point { 350 fn hash<H>(&self, state: &mut H) 351 where 352 H: Hasher, 353 { 354 self.latitude.hash(state); 355 self.longitude.hash(state); 356 } 357} 358 359impl Eq for Point {} 360 361``` 362 363Lastly, we need implement two helper functions: `in_range` and `calc_distance`. We'll use them 364when performing feature lookups. You can find them in 365[tonic-examples/src/routeguide/server.rs][in-range-fn]. 366 367[route-guide-db]: https://github.com/hyperium/tonic/blob/master/tonic-examples/data/route_guide_db.json 368[data-module]: https://github.com/hyperium/tonic/blob/master/tonic-examples/src/routeguide/data.rs 369[in-range-fn]: https://github.com/hyperium/tonic/blob/master/tonic-examples/src/routeguide/server.rs#L188 370 371#### Request and Response types 372All our service methods receive a `tonic::Request<T>` and return a 373`Result<tonic::Response<T>, tonic::Status>`. The concrete type of `T` depends on how our methods 374are declared in our *service* `.proto` definition. It can be either: 375 376- A single value, e.g `Point`, `Rectangle`, or even a message type that includes a repeated field. 377- A stream of values, e.g. `impl Stream<Item = Result<Feature, tonic::Status>>`. 378 379#### Simple RPC 380Let's look at the simplest method first, `get_feature`, which just gets a `tonic::Request<Point>` 381from the client and tries to find a feature at the given `Point`. If no feature is found, it returns 382an empty one. 383 384```rust 385async fn get_feature(&self, request: Request<Point>) -> Result<Response<Feature>, Status> { 386 for feature in &self.features[..] { 387 if feature.location.as_ref() == Some(request.get_ref()) { 388 return Ok(Response::new(feature.clone())); 389 } 390 } 391 392 let response = Response::new(Feature { 393 name: "".to_string(), 394 location: None, 395 }); 396 397 Ok(response) 398} 399``` 400 401 402#### Server-side streaming RPC 403Now let's look at one of our streaming RPCs. `list_features` is a server-side streaming RPC, so we 404need to send back multiple `Feature`s to our client. 405 406```rust 407type ListFeaturesStream = mpsc::Receiver<Result<Feature, Status>>; 408 409async fn list_features( 410 &self, 411 request: Request<Rectangle>, 412) -> Result<Response<Self::ListFeaturesStream>, Status> { 413 let (mut tx, rx) = mpsc::channel(4); 414 let features = self.features.clone(); 415 416 tokio::spawn(async move { 417 for feature in &features[..] { 418 if in_range(feature.location.as_ref().unwrap(), request.get_ref()) { 419 tx.send(Ok(feature.clone())).await.unwrap(); 420 } 421 } 422 }); 423 424 Ok(Response::new(rx)) 425} 426``` 427 428Like `get_feature`, `list_features`'s input is a single message, a `Rectangle` in this 429case. This time, however, we need to return a stream of values, rather than a single one. 430We create a channel and spawn a new asynchronous task where we perform a lookup, sending 431the features that satisfy our constraints into the channel. 432 433The `Stream` half of the channel is returned to the caller, wrapped in a `tonic::Response`. 434 435 436#### Client-side streaming RPC 437Now let's look at something a little more complicated: the client-side streaming method 438`record_route`, where we get a stream of `Point`s from the client and return a single `RouteSummary` 439with information about their trip. As you can see, this time the method receives a 440`tonic::Request<tonic::Streaming<Point>>`. 441 442```rust 443use std::time::Instant; 444use futures::StreamExt; 445``` 446 447```rust 448async fn record_route( 449 &self, 450 request: Request<tonic::Streaming<Point>>, 451) -> Result<Response<RouteSummary>, Status> { 452 let stream = request.into_inner(); 453 futures::pin_mut!(stream); 454 455 let mut summary = RouteSummary::default(); 456 let mut last_point = None; 457 let now = Instant::now(); 458 459 while let Some(point) = stream.next().await { 460 let point = point?; 461 summary.point_count += 1; 462 463 for feature in &self.features[..] { 464 if feature.location.as_ref() == Some(&point) { 465 summary.feature_count += 1; 466 } 467 } 468 469 if let Some(ref last_point) = last_point { 470 summary.distance += calc_distance(last_point, &point); 471 } 472 473 last_point = Some(point); 474 } 475 476 summary.elapsed_time = now.elapsed().as_secs() as i32; 477 478 Ok(Response::new(summary)) 479} 480``` 481 482`record_route` is conceptually simple: we get a stream of `Points` and fold it into a `RouteSummary`. 483In other words, we build a summary value as we process each `Point` in our stream, one by one. 484When there are no more `Points` in our stream, we return the `RouteSummary` wrapped in a 485`tonic::Response`. 486 487#### Bidirectional streaming RPC 488Finally, let's look at our bidirectional streaming RPC `route_chat`, which receives a stream 489of `RouteNote`s and returns a stream of `RouteNote`s. 490 491```rust 492type RouteChatStream = Pin<Box<dyn Stream<Item = Result<RouteNote, Status>> + Send + 'static>>; 493 494async fn route_chat( 495 &self, 496 request: Request<tonic::Streaming<RouteNote>>, 497) -> Result<Response<Self::RouteChatStream>, Status> { 498 let mut notes = HashMap::new(); 499 let stream = request.into_inner(); 500 501 let output = async_stream::try_stream! { 502 futures::pin_mut!(stream); 503 504 while let Some(note) = stream.next().await { 505 let note = note?; 506 507 let location = note.location.clone().unwrap(); 508 509 let location_notes = notes.entry(location).or_insert(vec![]); 510 location_notes.push(note); 511 512 for note in location_notes { 513 yield note.clone(); 514 } 515 } 516 }; 517 518 Ok(Response::new(Box::pin(output) 519 as Pin< 520 Box<dyn Stream<Item = Result<RouteNote, Status>> + Send + Sync + 'static>, 521 >)) 522 523} 524``` 525 526`route_chat` uses the [async-stream] crate to perform an asynchronous transformation 527from one (input) stream to another (output) stream. As the input is processed, each value is 528inserted into the notes map, yielding a clone of the original `RouteNote`. The resulting stream 529is then returned to the caller. Neat. 530 531**Note**: The funky `as` cast is needed due to a limitation in the rust compiler. This is expected 532to be fixed soon. 533 534[async-stream]: https://github.com/tokio-rs/async-stream 535 536### Starting the server 537 538Once we've implemented all our methods, we also need to start up a gRPC server so that clients can 539actually use our service. This is how our `main` function looks like: 540 541```rust 542mod data; 543use tonic::transport::Server; 544``` 545 546```rust 547#[tokio::main] 548async fn main() -> Result<(), Box<dyn std::error::Error>> { 549 let addr = "[::1]:10000".parse().unwrap(); 550 551 let route_guide = RouteGuide { 552 features: Arc::new(data::load()), 553 }; 554 555 let svc = server::RouteGuideServer::new(route_guide); 556 557 Server::builder() 558 .add_service(svc) 559 .serve(addr) 560 .await?; 561 562 Ok(()) 563} 564``` 565 566To handle requests, `Tonic` uses [Tower] and [hyper] internally. What this means, 567among other things, is that we have a flexible and composable stack we can build on top of. We can, 568for example, add an [interceptor][authentication-example] or implement [routing][router-example]. 569In the future, Tonic may include higher level support for routing and interceptors. 570 571 572[Tower]: https://github.com/tower-rs 573[hyper]: https://github.com/hyperium/hyper 574[authentication-example]: https://github.com/hyperium/tonic/blob/master/tonic-examples/src/authentication/server.rs#L54 575[router-example]: https://github.com/hyperium/tonic/blob/master/tonic-interop/src/bin/server.rs#L73 576 577<a name="client"></a> 578## Creating the client 579 580In this section, we'll look at creating a Tonic client for our `RouteGuide` service. You can see our 581complete example client code in [tonic-examples/src/routeguide/client.rs][routeguide-client]. 582 583Our crate will have two binary targets: `routeguide-client` and `routeguide-server`. We need to 584edit our `Cargo.toml` accordingly: 585 586```toml 587[[bin]] 588name = "routeguide-server" 589path = "src/server.rs" 590 591[[bin]] 592name = "routeguide-client" 593path = "src/client.rs" 594``` 595 596Rename `main.rs` to `server.rs` and create a new file `client.rs`. 597 598```shell 599$ mv src/main.rs src/server.rs 600$ touch src/client.rs 601``` 602 603To call service methods, we first need to create a gRPC *client* to communicate with the server. 604 605```rust 606pub mod route_guide { 607 tonic::include_proto!("routeguide"); 608} 609 610use route_guide::{client::RouteGuideClient, Point, Rectangle, RouteNote}; 611 612#[tokio::main] 613async fn main() -> Result<(), Box<dyn std::error::Error>> { 614 let mut client = RouteGuideClient::connect("http://[::1]:10000").await?; 615 616 Ok(()) 617} 618``` 619 620Same as in the server implementation, we start by bringing our generated code into scope. We then 621create a client in our main function, passing the server's full URL to `RouteGuideClient::connect`. 622Our client is now ready to make service calls. Note that `client` is mutable, this is because it 623needs to manage internal state. 624 625[routeguide-client]: https://github.com/hyperium/tonic/blob/master/tonic-examples/src/routeguide/client.rs 626 627 628### Calling service methods 629Now let's look at how we call our service methods. Note that in Tonic, RPCs are asynchronous, 630which means that RPC calls need to be `.await`ed. 631 632#### Simple RPC 633Calling the simple RPC `get_feature` is as straightforward as calling a local method: 634 635```rust 636use tonic::Request; 637``` 638 639```rust 640let response = client 641 .get_feature(Request::new(Point { 642 latitude: 409146138, 643 longitude: -746188906, 644 })) 645 .await?; 646 647println!("RESPONSE = {:?}", response); 648``` 649We call the `get_feature` client method, passing a single `Point` value wrapped in a 650`tonic::Request`. We get a `Result<tonic::Response<Feature>, tonic::Status>` back. 651 652#### Server-side streaming RPC 653Here's where we call the server-side streaming method `list_features`, which returns a stream of 654geographical `Feature`s. 655 656```rust 657use tonic::transport::Channel; 658use std::error::Error; 659``` 660 661```rust 662async fn print_features(client: &mut RouteGuideClient<Channel>) -> Result<(), Box<dyn Error>> { 663 let rectangle = Rectangle { 664 lo: Some(Point { 665 latitude: 400000000, 666 longitude: -750000000, 667 }), 668 hi: Some(Point { 669 latitude: 420000000, 670 longitude: -730000000, 671 }), 672 }; 673 674 let mut stream = client 675 .list_features(Request::new(rectangle)) 676 .await? 677 .into_inner(); 678 679 while let Some(feature) = stream.message().await? { 680 println!("NOTE = {:?}", feature); 681 } 682 683 Ok(()) 684} 685``` 686 687As in the simple RPC, we pass a single value request. However, instead of getting a 688single value back, we get a stream of `Features`. 689 690We use the the `message()` method from the `tonic::Streaming` struct to repeatedly read in the 691server's responses to a response protocol buffer object (in this case a `Feature`) until there are 692no more messages left in the stream. 693 694#### Client-side streaming RPC 695The client-side streaming method `record_route` takes a stream of `Point`s and returns a single 696`RouteSummary` value. 697 698```rust 699use rand::rngs::ThreadRng; 700use rand::Rng; 701use futures::stream; 702``` 703 704```rust 705async fn run_record_route(client: &mut RouteGuideClient<Channel>) -> Result<(), Box<dyn Error>> { 706 let mut rng = rand::thread_rng(); 707 let point_count: i32 = rng.gen_range(2, 100); 708 709 let mut points = vec![]; 710 for _ in 0..=point_count { 711 points.push(random_point(&mut rng)) 712 } 713 714 println!("Traversing {} points", points.len()); 715 let request = Request::new(stream::iter(points)); 716 717 match client.record_route(request).await { 718 Ok(response) => println!("SUMMARY: {:?}", response.into_inner()), 719 Err(e) => println!("something went wrong: {:?}", e), 720 } 721 722 Ok(()) 723} 724``` 725 726```rust 727fn random_point(rng: &mut ThreadRng) -> Point { 728 let latitude = (rng.gen_range(0, 180) - 90) * 10_000_000; 729 let longitude = (rng.gen_range(0, 360) - 180) * 10_000_000; 730 Point { 731 latitude, 732 longitude, 733 } 734} 735``` 736 737We build a vector of a random number of `Point` values (between 2 and 100) and then convert 738it into a `Stream` using the `futures::stream::iter` function. This is a cheap an easy way to get 739a stream suitable for passing into our service method. The resulting stream is then wrapped in a 740`tonic::Request`. 741 742 743#### Bidirectional streaming RPC 744 745Finally, let's look at our bidirectional streaming RPC. The `route_chat` method takes a stream 746of `RouteNotes` and returns either another stream of `RouteNotes` or an error. 747 748```rust 749use std::time::{Duration, Instant}; 750use tokio::timer::Interval; 751``` 752 753```rust 754async fn run_route_chat(client: &mut RouteGuideClient<Channel>) -> Result<(), Box<dyn Error>> { 755 let start = Instant::now(); 756 757 let outbound = async_stream::stream! { 758 let mut interval = Interval::new_interval(Duration::from_secs(1)); 759 760 while let Some(time) = interval.next().await { 761 let elapsed = time.duration_since(start); 762 let note = RouteNote { 763 location: Some(Point { 764 latitude: 409146138 + elapsed.as_secs() as i32, 765 longitude: -746188906, 766 }), 767 message: format!("at {:?}", elapsed), 768 }; 769 770 yield note; 771 } 772 }; 773 774 let request = Request::new(outbound); 775 let response = client.route_chat(request).await?; 776 let mut inbound = response.into_inner(); 777 778 while let Some(note) = inbound.message().await? { 779 println!("NOTE = {:?}", note); 780 } 781 782 Ok(()) 783} 784``` 785In this case, we use the [async-stream] crate to generate our outbound stream, yielding 786`RouteNote` values in one second intervals. We then iterate over the stream returned by 787the server, printing each value in the stream. 788 789## Try it out! 790 791### Run the server 792```shell 793$ cargo run --bin routeguide-server 794``` 795 796### Run the client 797```shell 798$ cargo run --bin routeguide-client 799``` 800 801## Appendix 802 803<a name="tonic-build"></a> 804### tonic_build configuration 805 806Tonic's default code generation configuration is convenient for self contained examples and small 807projects. However, there are some cases when we need a slightly different workflow. For example: 808 809- When building rust clients and servers in different crates. 810- When building a rust client or server (or both) as part of a larger, multi-language project. 811- When we want editor support for the generate code and our editor does not index the generated 812files in the default location. 813 814More generally, whenever we want to keep our `.proto` definitions in a central place and generate 815code for different crates or different languages, the default configuration is not enough. 816 817Luckily, `tonic_build` can be configured to fit whatever workflow we need. Here are just two 818possibilities: 819 8201) We can keep our `.proto` definitions in a separate crate and generate our code on demand, as 821opposed to at build time, placing the resulting modules wherever we need them. 822 823`main.rs` 824 825```rust 826fn main() { 827 tonic_build::configure() 828 .build_client(false) 829 .out_dir("another_crate/src/pb") 830 .compile(&["path/my_proto.proto"], &["path"]) 831 .expect("failed to compile protos"); 832} 833``` 834 835On `cargo run`, this will generate code for the client only, and place the resulting file in 836`another_crate/src/pb`. 837 8382) Similarly, we could also keep the `.proto` definitions in a separate crate and then use that 839crate as a direct dependency wherever we need it. 840 841