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