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