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