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