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