xref: /tonic/examples/routeguide-tutorial.md (revision 61555ff2)
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