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.2" 184prost = "0.6" 185futures-core = "0.3" 186futures-util = "0.3" 187tokio = { version = "0.2", features = ["macros", "sync", "stream", "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.2" 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/06_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 Pin< 521 Box<dyn Stream<Item = Result<RouteNote, Status>> + Send + Sync + 'static>, 522 >)) 523 524} 525``` 526 527`route_chat` uses the [async-stream] crate to perform an asynchronous transformation 528from one (input) stream to another (output) stream. As the input is processed, each value is 529inserted into the notes map, yielding a clone of the original `RouteNote`. The resulting stream 530is then returned to the caller. Neat. 531 532**Note**: The funky `as` cast is needed due to a limitation in the rust compiler. This is expected 533to be fixed soon. 534 535[async-stream]: https://github.com/tokio-rs/async-stream 536 537### Starting the server 538 539Once we've implemented all our methods, we also need to start up a gRPC server so that clients can 540actually use our service. This is how our `main` function looks like: 541 542```rust 543mod data; 544use tonic::transport::Server; 545``` 546 547```rust 548#[tokio::main] 549async fn main() -> Result<(), Box<dyn std::error::Error>> { 550 let addr = "[::1]:10000".parse().unwrap(); 551 552 let route_guide = RouteGuideService { 553 features: Arc::new(data::load()), 554 }; 555 556 let svc = RouteGuideServer::new(route_guide); 557 558 Server::builder().add_service(svc).serve(addr).await?; 559 560 Ok(()) 561} 562``` 563 564To handle requests, `Tonic` uses [Tower] and [hyper] internally. What this means, 565among other things, is that we have a flexible and composable stack we can build on top of. We can, 566for example, add an [interceptor][authentication-example] to process requests before they reach our service 567methods. 568 569 570[Tower]: https://github.com/tower-rs 571[hyper]: https://github.com/hyperium/hyper 572[authentication-example]: https://github.com/hyperium/tonic/blob/master/examples/src/authentication/server.rs#L56 573 574<a name="client"></a> 575## Creating the client 576 577In this section, we'll look at creating a Tonic client for our `RouteGuide` service. You can see our 578complete example client code in [examples/src/routeguide/client.rs][routeguide-client]. 579 580Our crate will have two binary targets: `routeguide-client` and `routeguide-server`. We need to 581edit our `Cargo.toml` accordingly: 582 583```toml 584[[bin]] 585name = "routeguide-server" 586path = "src/server.rs" 587 588[[bin]] 589name = "routeguide-client" 590path = "src/client.rs" 591``` 592 593Rename `main.rs` to `server.rs` and create a new file `client.rs`. 594 595```shell 596$ mv src/main.rs src/server.rs 597$ touch src/client.rs 598``` 599 600To call service methods, we first need to create a gRPC *client* to communicate with the server. Like in the server 601case, we'll start by bringing the generated code into scope: 602 603```rust 604pub mod routeguide { 605 tonic::include_proto!("routeguide"); 606} 607 608use routeguide::route_guide_client::RouteGuideClient; 609use routeguide::{Point, Rectangle, RouteNote}; 610 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/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_util::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; 750use tokio::time; 751``` 752 753```rust 754async fn run_route_chat(client: &mut RouteGuideClient<Channel>) -> Result<(), Box<dyn Error>> { 755 let start = time::Instant::now(); 756 757 let outbound = async_stream::stream! { 758 let mut interval = time::interval(Duration::from_secs(1)); 759 760 while let time = interval.tick().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 response = client.route_chat(Request::new(outbound)).await?; 775 let mut inbound = response.into_inner(); 776 777 while let Some(note) = inbound.message().await? { 778 println!("NOTE = {:?}", note); 779 } 780 781 Ok(()) 782} 783``` 784In this case, we use the [async-stream] crate to generate our outbound stream, yielding 785`RouteNote` values in one second intervals. We then iterate over the stream returned by 786the server, printing each value in the stream. 787 788## Try it out! 789 790### Run the server 791```shell 792$ cargo run --bin routeguide-server 793``` 794 795### Run the client 796```shell 797$ cargo run --bin routeguide-client 798``` 799 800## Appendix 801 802<a name="tonic-build"></a> 803### tonic_build configuration 804 805Tonic's default code generation configuration is convenient for self contained examples and small 806projects. However, there are some cases when we need a slightly different workflow. For example: 807 808- When building rust clients and servers in different crates. 809- When building a rust client or server (or both) as part of a larger, multi-language project. 810- When we want editor support for the generate code and our editor does not index the generated 811files in the default location. 812 813More generally, whenever we want to keep our `.proto` definitions in a central place and generate 814code for different crates or different languages, the default configuration is not enough. 815 816Luckily, `tonic_build` can be configured to fit whatever workflow we need. Here are just two 817possibilities: 818 8191) We can keep our `.proto` definitions in a separate crate and generate our code on demand, as 820opposed to at build time, placing the resulting modules wherever we need them. 821 822`main.rs` 823 824```rust 825fn main() { 826 tonic_build::configure() 827 .build_client(false) 828 .out_dir("another_crate/src/pb") 829 .compile(&["path/my_proto.proto"], &["path"]) 830 .expect("failed to compile protos"); 831} 832``` 833 834On `cargo run`, this will generate code for the client only, and place the resulting file in 835`another_crate/src/pb`. 836 8372) Similarly, we could also keep the `.proto` definitions in a separate crate and then use that 838crate as a direct dependency wherever we need it. 839 840