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.5" 184prost = "0.8" 185futures-core = "0.3" 186futures-util = "0.3" 187tokio = { version = "1.0", features = ["rt-multi-thread", "macros", "sync", "time"] } 188tokio-stream = "0.1" 189 190async-stream = "0.2" 191serde = { version = "1.0", features = ["derive"] } 192serde_json = "1.0" 193rand = "0.7" 194 195[build-dependencies] 196tonic-build = "0.5" 197``` 198 199Create a `build.rs` file at the root of your crate: 200 201```rust 202fn main() { 203 tonic_build::compile_protos("proto/route_guide.proto") 204 .unwrap_or_else(|e| panic!("Failed to compile protos {:?}", e)); 205} 206``` 207 208```shell 209$ cargo build 210``` 211 212That's it. The generated code contains: 213 214- Struct definitions for message types `Point`, `Rectangle`, `Feature`, `RouteNote`, `RouteSummary`. 215- A service trait we'll need to implement: `route_guide_server::RouteGuide`. 216- A client type we'll use to call the server: `route_guide_client::RouteGuideClient<T>`. 217 218If your are curious as to where the generated files are, keep reading. The mystery will be revealed 219soon! We can now move on to the fun part. 220 221[PROST!]: https://github.com/danburkert/prost 222 223## Creating the server 224 225First let's look at how we create a `RouteGuide` server. If you're only interested in creating gRPC 226clients, you can skip this section and go straight to [Creating the client](#client) 227(though you might find it interesting anyway!). 228 229There are two parts to making our `RouteGuide` service do its job: 230 231- Implementing the service trait generated from our service definition. 232- Running a gRPC server to listen for requests from clients. 233 234You can find our example `RouteGuide` server in 235[examples/src/routeguide/server.rs][routeguide-server]. 236 237[routeguide-server]: https://github.com/hyperium/tonic/blob/master/examples/src/routeguide/server.rs 238 239### Implementing the RouteGuide server trait 240 241We can start by defining a struct to represent our service, we can do this on `main.rs` for now: 242 243```rust 244#[derive(Debug)] 245struct RouteGuideService; 246``` 247 248Next, we need to implement the `route_guide_server::RouteGuide` trait that is generated in our build step. 249The generated code is placed inside our target directory, in a location defined by the `OUT_DIR` 250environment variable that is set by cargo. For our example, this means you can find the generated 251code in a path similar to `target/debug/build/routeguide/out/routeguide.rs`. 252 253You can learn more about `build.rs` and the `OUT_DIR` environment variable in the [cargo book]. 254 255We can use Tonic's `include_proto` macro to bring the generated code into scope: 256 257```rust 258pub mod routeguide { 259 tonic::include_proto!("routeguide"); 260} 261 262use routeguide::route_guide_server::{RouteGuide, RouteGuideServer}; 263use routeguide::{Feature, Point, Rectangle, RouteNote, RouteSummary}; 264``` 265 266**Note**: The token passed to the `include_proto` macro (in our case "routeguide") is the name of 267the package declared in our `.proto` file, not a filename, e.g "routeguide.rs". 268 269With this in place, we can stub out our service implementation: 270 271```rust 272use futures_core::Stream; 273use std::pin::Pin; 274use std::sync::Arc; 275use tokio::sync::mpsc; 276use tonic::{Request, Response, Status}; 277use tokio_stream::wrappers::ReceiverStream; 278``` 279 280```rust 281#[tonic::async_trait] 282impl RouteGuide for RouteGuideService { 283 async fn get_feature(&self, _request: Request<Point>) -> Result<Response<Feature>, Status> { 284 unimplemented!() 285 } 286 287 type ListFeaturesStream = ReceiverStream<Result<Feature, Status>>; 288 289 async fn list_features( 290 &self, 291 _request: Request<Rectangle>, 292 ) -> Result<Response<Self::ListFeaturesStream>, Status> { 293 unimplemented!() 294 } 295 296 async fn record_route( 297 &self, 298 _request: Request<tonic::Streaming<Point>>, 299 ) -> Result<Response<RouteSummary>, Status> { 300 unimplemented!() 301 } 302 303 type RouteChatStream = Pin<Box<dyn Stream<Item = Result<RouteNote, Status>> + Send + Sync + 'static>>; 304 305 async fn route_chat( 306 &self, 307 _request: Request<tonic::Streaming<RouteNote>>, 308 ) -> Result<Response<Self::RouteChatStream>, Status> { 309 unimplemented!() 310 } 311} 312``` 313 314**Note**: The `tonic::async_trait` attribute macro adds support for async functions in traits. It 315uses [async-trait] internally. You can learn more about `async fn` in traits in the [async book]. 316 317 318[cargo book]: https://doc.rust-lang.org/cargo/reference/environment-variables.html#environment-variables-cargo-sets-for-build-scripts 319[async-trait]: https://github.com/dtolnay/async-trait 320[async book]: https://rust-lang.github.io/async-book/07_workarounds/05_async_in_traits.html 321 322### Server state 323Our service needs access to an immutable list of features. When the server starts, we are going to 324deserialize them from a json file and keep them around as our only piece of shared state: 325 326```rust 327#[derive(Debug)] 328pub struct RouteGuideService { 329 features: Arc<Vec<Feature>>, 330} 331``` 332 333Create the json data file and a helper module to read and deserialize our features. 334 335```shell 336$ mkdir data && touch data/route_guide_db.json 337$ touch src/data.rs 338``` 339 340You can find our example json data in [examples/data/route_guide_db.json][route-guide-db] and 341the corresponding `data` module to load and deserialize it in 342[examples/routeguide/data.rs][data-module]. 343 344**Note:** If you are following along, you'll need to change the data file's path from 345`examples/data/route_guide_db.json` to `data/route_guide_db.json`. 346 347Next, we need to implement `Hash` and `Eq` for `Point`, so we can use point values as map keys: 348 349```rust 350use std::hash::{Hasher, Hash}; 351``` 352 353```rust 354impl Hash for Point { 355 fn hash<H>(&self, state: &mut H) 356 where 357 H: Hasher, 358 { 359 self.latitude.hash(state); 360 self.longitude.hash(state); 361 } 362} 363 364impl Eq for Point {} 365 366``` 367 368Lastly, we need implement two helper functions: `in_range` and `calc_distance`. We'll use them 369when performing feature lookups. You can find them in 370[examples/src/routeguide/server.rs][in-range-fn]. 371 372[route-guide-db]: https://github.com/hyperium/tonic/blob/master/examples/data/route_guide_db.json 373[data-module]: https://github.com/hyperium/tonic/blob/master/examples/src/routeguide/data.rs 374[in-range-fn]: https://github.com/hyperium/tonic/blob/master/examples/src/routeguide/server.rs#L174 375 376#### Request and Response types 377All our service methods receive a `tonic::Request<T>` and return a 378`Result<tonic::Response<T>, tonic::Status>`. The concrete type of `T` depends on how our methods 379are declared in our *service* `.proto` definition. It can be either: 380 381- A single value, e.g `Point`, `Rectangle`, or even a message type that includes a repeated field. 382- A stream of values, e.g. `impl Stream<Item = Result<Feature, tonic::Status>>`. 383 384#### Simple RPC 385Let's look at the simplest method first, `get_feature`, which just gets a `tonic::Request<Point>` 386from the client and tries to find a feature at the given `Point`. If no feature is found, it returns 387an empty one. 388 389```rust 390async fn get_feature(&self, request: Request<Point>) -> Result<Response<Feature>, Status> { 391 for feature in &self.features[..] { 392 if feature.location.as_ref() == Some(request.get_ref()) { 393 return Ok(Response::new(feature.clone())); 394 } 395 } 396 397 Ok(Response::new(Feature::default())) 398} 399``` 400 401 402#### Server-side streaming RPC 403Now let's look at one of our streaming RPCs. `list_features` is a server-side streaming RPC, so we 404need to send back multiple `Feature`s to our client. 405 406```rust 407type ListFeaturesStream = ReceiverStream<Result<Feature, Status>>; 408 409async fn list_features( 410 &self, 411 request: Request<Rectangle>, 412) -> Result<Response<Self::ListFeaturesStream>, Status> { 413 let (mut tx, rx) = mpsc::channel(4); 414 let features = self.features.clone(); 415 416 tokio::spawn(async move { 417 for feature in &features[..] { 418 if in_range(feature.location.as_ref().unwrap(), request.get_ref()) { 419 tx.send(Ok(feature.clone())).await.unwrap(); 420 } 421 } 422 }); 423 424 Ok(Response::new(ReceiverStream::new(rx))) 425} 426``` 427 428Like `get_feature`, `list_features`'s input is a single message, a `Rectangle` in this 429case. This time, however, we need to return a stream of values, rather than a single one. 430We create a channel and spawn a new asynchronous task where we perform a lookup, sending 431the features that satisfy our constraints into the channel. 432 433The `Stream` half of the channel is returned to the caller, wrapped in a `tonic::Response`. 434 435 436#### Client-side streaming RPC 437Now let's look at something a little more complicated: the client-side streaming method 438`record_route`, where we get a stream of `Point`s from the client and return a single `RouteSummary` 439with information about their trip. As you can see, this time the method receives a 440`tonic::Request<tonic::Streaming<Point>>`. 441 442```rust 443use std::time::Instant; 444use futures_util::StreamExt; 445``` 446 447```rust 448async fn record_route( 449 &self, 450 request: Request<tonic::Streaming<Point>>, 451) -> Result<Response<RouteSummary>, Status> { 452 let mut stream = request.into_inner(); 453 454 let mut summary = RouteSummary::default(); 455 let mut last_point = None; 456 let now = Instant::now(); 457 458 while let Some(point) = stream.next().await { 459 let point = point?; 460 summary.point_count += 1; 461 462 for feature in &self.features[..] { 463 if feature.location.as_ref() == Some(&point) { 464 summary.feature_count += 1; 465 } 466 } 467 468 if let Some(ref last_point) = last_point { 469 summary.distance += calc_distance(last_point, &point); 470 } 471 472 last_point = Some(point); 473 } 474 475 summary.elapsed_time = now.elapsed().as_secs() as i32; 476 477 Ok(Response::new(summary)) 478} 479``` 480 481`record_route` is conceptually simple: we get a stream of `Points` and fold it into a `RouteSummary`. 482In other words, we build a summary value as we process each `Point` in our stream, one by one. 483When there are no more `Points` in our stream, we return the `RouteSummary` wrapped in a 484`tonic::Response`. 485 486#### Bidirectional streaming RPC 487Finally, let's look at our bidirectional streaming RPC `route_chat`, which receives a stream 488of `RouteNote`s and returns a stream of `RouteNote`s. 489 490```rust 491use std::collections::HashMap; 492``` 493 494```rust 495type RouteChatStream = 496 Pin<Box<dyn Stream<Item = Result<RouteNote, Status>> + Send + Sync + 'static>>; 497 498 499async fn route_chat( 500 &self, 501 request: Request<tonic::Streaming<RouteNote>>, 502) -> Result<Response<Self::RouteChatStream>, Status> { 503 let mut notes = HashMap::new(); 504 let mut stream = request.into_inner(); 505 506 let output = async_stream::try_stream! { 507 while let Some(note) = stream.next().await { 508 let note = note?; 509 510 let location = note.location.clone().unwrap(); 511 512 let location_notes = notes.entry(location).or_insert(vec![]); 513 location_notes.push(note); 514 515 for note in location_notes { 516 yield note.clone(); 517 } 518 } 519 }; 520 521 Ok(Response::new(Box::pin(output) 522 as Self::RouteChatStream)) 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