xref: /tonic/examples/routeguide-tutorial.md (revision 3a5c66d5)
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        NOTE = RouteNote { location: Some(Point { latitude: 409146139, longitude: -746188906 }), message: "at 1.000319208s" }
71
72If you scroll up you should see the output of the other 3 request types: simple rpc, server-side
73streaming and client-side streaming.
74
75
76[readme]: https://github.com/hyperium/tonic#getting-started
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[tonic-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/tonic-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]
183async-stream = "0.1.2"
184bytes = "0.4"
185futures-preview = { version = "0.3.0-alpha.19", default-features = false, features = ["alloc"]}
186serde = { version = "1.0", features = ["derive"] }
187serde_json = "1.0"
188prost = "0.5"
189rand = "0.7.2"
190tokio = "0.2.0-alpha.6"
191tonic = "0.1.0-alpha.5"
192
193[build-dependencies]
194tonic-build = "0.1.0-alpha.5"
195```
196
197Create a `build.rs` file at the root of your crate:
198
199```rust
200fn main() {
201    tonic_build::compile_protos("proto/route_guide.proto")
202        .unwrap_or_else(|e| panic!("Failed to compile protos {:?}", e));
203}
204```
205
206```shell
207$ cargo build
208```
209
210That's it. The generated code contains:
211
212- Struct definitions for message types `Point`, `Rectangle`, `Feature`, `RouteNote`, `RouteSummary`.
213- A service trait we'll need to implement: `server::RouteGuide`.
214- A client type we'll use to call the server: `client::RouteGuideClient<T>`.
215
216If your are curious as to where the generated files are, keep reading. The mystery will be revealed
217soon! We can now move on to the fun part.
218
219[PROST!]: https://github.com/danburkert/prost
220
221## Creating the server
222
223First let's look at how we create a `RouteGuide` server. If you're only interested in creating gRPC
224clients, you can skip this section and go straight to [Creating the client](#client)
225(though you might find it interesting anyway!).
226
227There are two parts to making our `RouteGuide` service do its job:
228
229- Implementing the service trait generated from our service definition.
230- Running a gRPC server to listen for requests from clients.
231
232You can find our example `RouteGuide` server in
233[tonic-examples/src/routeguide/server.rs][routeguide-server].
234
235[routeguide-server]: https://github.com/hyperium/tonic/blob/master/tonic-examples/src/routeguide/server.rs
236
237### Implementing the server::RouteGuide trait
238
239We can start by defining a struct to represent our service, we can do this on `main.rs` for now:
240
241```rust
242#[derive(Debug)]
243struct RouteGuide;
244```
245
246Next, we need to implement the `server::RouteGuide` trait that is generated in our build step.
247The generated code is placed inside our target directory, in a location defined by the `OUT_DIR`
248environment variable that is set by cargo. For our example, this means you can find the generated
249code in a path similar to `target/debug/build/routeguide/out/routeguide.rs`.
250
251You can learn more about `build.rs` and the `OUT_DIR` environment variable in the [cargo book].
252
253We can use Tonic's `include_proto` macro to bring the generated code into scope:
254
255```rust
256pub mod routeguide {
257    tonic::include_proto!("routeguide");
258}
259
260use routeguide::{server, Feature, Point, Rectangle, RouteNote, RouteSummary};
261```
262
263**Note**: The token passed to the `include_proto` macro (in our case "routeguide") is the name of
264the package declared in in our `.proto` file, not a filename, e.g "routeguide.rs".
265
266With this in place, we can stub out our service implementation:
267
268```rust
269use tonic::{Request, Response, Status};
270use tokio::sync::mpsc;
271use std::pin::Pin;
272use futures::Stream;
273```
274
275```rust
276#[tonic::async_trait]
277impl server::RouteGuide for RouteGuide {
278    async fn get_feature(&self, _request: Request<Point>) -> Result<Response<Feature>, Status> {
279        unimplemented!()
280    }
281
282    type ListFeaturesStream = mpsc::Receiver<Result<Feature, Status>>;
283
284    async fn list_features(
285        &self,
286        _request: Request<Rectangle>,
287    ) -> Result<Response<Self::ListFeaturesStream>, Status> {
288        unimplemented!()
289    }
290
291    async fn record_route(
292        &self,
293        _request: Request<tonic::Streaming<Point>>,
294    ) -> Result<Response<RouteSummary>, Status> {
295        unimplemented!()
296    }
297
298    type RouteChatStream = Pin<Box<dyn Stream<Item = Result<RouteNote, Status>> + Send + Sync + 'static>>;
299
300    async fn route_chat(
301        &self,
302        _request: Request<tonic::Streaming<RouteNote>>,
303    ) -> Result<Response<Self::RouteChatStream>, Status> {
304        unimplemented!()
305    }
306}
307```
308
309**Note**: The `tonic::async_trait` attribute macro adds support for async functions in traits. It
310uses [async-trait] internally. You can learn more about `async fn` in traits in the [async book].
311
312
313[cargo book]: https://doc.rust-lang.org/cargo/reference/environment-variables.html#environment-variables-cargo-sets-for-build-scripts
314[async-trait]: https://github.com/dtolnay/async-trait
315[async book]: https://rust-lang.github.io/async-book/07_workarounds/06_async_in_traits.html
316
317### Server state
318Our service needs access to an immutable list of features. When the server starts, we are going to
319deserialize them from a json file and keep them around as our only piece of shared state:
320
321```rust
322#[derive(Debug)]
323pub struct RouteGuide {
324    features: Arc<Vec<Feature>>,
325}
326```
327
328Create the json data file and a helper module to read and deserialize our features.
329
330```shell
331$ mkdir data && touch data/route_guide_db.json
332$ touch src/data.rs
333```
334
335You can find our example json data in [tonic-examples/data/route_guide_db.json][route-guide-db] and
336the corresponding `data` module to load and deserialize it in
337[tonic-examples/routeguide/data.rs][data-module].
338
339**Note:** If you are following along, you'll need to change the data file's path  from
340`tonic-examples/data/route_guide_db.json` to `data/route_guide_db.json`.
341
342Next, we need to implement `Hash` and `Eq` for `Point`, so we can use point values as map keys:
343
344```rust
345use std::hash::{Hasher, Hash};
346```
347
348```rust
349impl Hash for Point {
350    fn hash<H>(&self, state: &mut H)
351    where
352        H: Hasher,
353    {
354        self.latitude.hash(state);
355        self.longitude.hash(state);
356    }
357}
358
359impl Eq for Point {}
360
361```
362
363Lastly, we need implement two helper functions: `in_range` and `calc_distance`. We'll use them
364when performing feature lookups. You can find them in
365[tonic-examples/src/routeguide/server.rs][in-range-fn].
366
367[route-guide-db]: https://github.com/hyperium/tonic/blob/master/tonic-examples/data/route_guide_db.json
368[data-module]: https://github.com/hyperium/tonic/blob/master/tonic-examples/src/routeguide/data.rs
369[in-range-fn]: https://github.com/hyperium/tonic/blob/master/tonic-examples/src/routeguide/server.rs#L188
370
371#### Request and Response types
372All our service methods receive a `tonic::Request<T>` and return a
373`Result<tonic::Response<T>, tonic::Status>`. The concrete type of `T` depends on how our methods
374are declared in our *service* `.proto` definition. It can be either:
375
376- A single value, e.g `Point`, `Rectangle`, or even a message type that includes a repeated field.
377- A stream of values, e.g. `impl Stream<Item = Result<Feature, tonic::Status>>`.
378
379#### Simple RPC
380Let's look at the simplest method first, `get_feature`, which just gets a `tonic::Request<Point>`
381from the client and tries to find a feature at the given `Point`. If no feature is found, it returns
382an empty one.
383
384```rust
385async fn get_feature(&self, request: Request<Point>) -> Result<Response<Feature>, Status> {
386    for feature in &self.features[..] {
387        if feature.location.as_ref() == Some(request.get_ref()) {
388            return Ok(Response::new(feature.clone()));
389        }
390    }
391
392    let response = Response::new(Feature {
393        name: "".to_string(),
394        location: None,
395    });
396
397    Ok(response)
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 = mpsc::Receiver<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(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::StreamExt;
445```
446
447```rust
448async fn record_route(
449    &self,
450    request: Request<tonic::Streaming<Point>>,
451) -> Result<Response<RouteSummary>, Status> {
452    let stream = request.into_inner();
453    futures::pin_mut!(stream);
454
455    let mut summary = RouteSummary::default();
456    let mut last_point = None;
457    let now = Instant::now();
458
459    while let Some(point) = stream.next().await {
460        let point = point?;
461        summary.point_count += 1;
462
463        for feature in &self.features[..] {
464            if feature.location.as_ref() == Some(&point) {
465                summary.feature_count += 1;
466            }
467        }
468
469        if let Some(ref last_point) = last_point {
470            summary.distance += calc_distance(last_point, &point);
471        }
472
473        last_point = Some(point);
474    }
475
476    summary.elapsed_time = now.elapsed().as_secs() as i32;
477
478    Ok(Response::new(summary))
479}
480```
481
482`record_route` is conceptually simple: we get a stream of `Points` and fold it into a `RouteSummary`.
483In other words, we build a summary value as we process each `Point` in our stream, one by one.
484When there are no more `Points` in our stream, we return the `RouteSummary` wrapped in a
485`tonic::Response`.
486
487#### Bidirectional streaming RPC
488Finally, let's look at our bidirectional streaming RPC `route_chat`, which receives a stream
489of `RouteNote`s and returns  a stream of `RouteNote`s.
490
491```rust
492type RouteChatStream = Pin<Box<dyn Stream<Item = Result<RouteNote, Status>> + Send + 'static>>;
493
494async fn route_chat(
495    &self,
496    request: Request<tonic::Streaming<RouteNote>>,
497) -> Result<Response<Self::RouteChatStream>, Status> {
498    let mut notes = HashMap::new();
499    let stream = request.into_inner();
500
501    let output = async_stream::try_stream! {
502        futures::pin_mut!(stream);
503
504        while let Some(note) = stream.next().await {
505            let note = note?;
506
507            let location = note.location.clone().unwrap();
508
509            let location_notes = notes.entry(location).or_insert(vec![]);
510            location_notes.push(note);
511
512            for note in location_notes {
513                yield note.clone();
514            }
515        }
516    };
517
518    Ok(Response::new(Box::pin(output)
519        as Pin<
520            Box<dyn Stream<Item = Result<RouteNote, Status>> + Send + Sync + 'static>,
521        >))
522
523}
524```
525
526`route_chat` uses the [async-stream] crate to perform an asynchronous transformation
527from one (input) stream to another (output) stream. As the input is processed, each value is
528inserted into the notes map, yielding a clone of the original `RouteNote`. The resulting stream
529is then returned to the caller. Neat.
530
531**Note**: The funky `as` cast is needed due to a limitation in the rust compiler. This is expected
532to be fixed soon.
533
534[async-stream]: https://github.com/tokio-rs/async-stream
535
536### Starting the server
537
538Once we've implemented all our methods, we also need to start up a gRPC server so that clients can
539actually use our service. This is how our `main` function looks like:
540
541```rust
542mod data;
543use tonic::transport::Server;
544```
545
546```rust
547#[tokio::main]
548async fn main() -> Result<(), Box<dyn std::error::Error>> {
549    let addr = "[::1]:10000".parse().unwrap();
550
551    let route_guide = RouteGuide {
552        features: Arc::new(data::load()),
553    };
554
555    let svc = server::RouteGuideServer::new(route_guide);
556
557    Server::builder()
558        .add_service(svc)
559        .serve(addr)
560        .await?;
561
562    Ok(())
563}
564```
565
566To handle requests, `Tonic` uses [Tower] and [hyper] internally. What this means,
567among other things, is that we have a flexible and composable stack we can build on top of. We can,
568for example, add an [interceptor][authentication-example] or implement [routing][router-example].
569In the future, Tonic may include higher level support for routing and interceptors.
570
571
572[Tower]: https://github.com/tower-rs
573[hyper]: https://github.com/hyperium/hyper
574[authentication-example]: https://github.com/hyperium/tonic/blob/master/tonic-examples/src/authentication/server.rs#L54
575[router-example]: https://github.com/hyperium/tonic/blob/master/tonic-interop/src/bin/server.rs#L73
576
577<a name="client"></a>
578## Creating the client
579
580In this section, we'll look at creating a Tonic client for our `RouteGuide` service. You can see our
581complete example client code in [tonic-examples/src/routeguide/client.rs][routeguide-client].
582
583Our crate will have two binary targets: `routeguide-client` and `routeguide-server`. We need to
584edit our `Cargo.toml` accordingly:
585
586```toml
587[[bin]]
588name = "routeguide-server"
589path = "src/server.rs"
590
591[[bin]]
592name = "routeguide-client"
593path = "src/client.rs"
594```
595
596Rename `main.rs` to `server.rs` and create a new file `client.rs`.
597
598```shell
599$ mv src/main.rs src/server.rs
600$ touch src/client.rs
601```
602
603To call service methods, we first need to create a gRPC *client* to communicate with the server.
604
605```rust
606pub mod route_guide {
607    tonic::include_proto!("routeguide");
608}
609
610use route_guide::{client::RouteGuideClient, Point, Rectangle, RouteNote};
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/tonic-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::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, Instant};
750use tokio::timer::Interval;
751```
752
753```rust
754async fn run_route_chat(client: &mut RouteGuideClient<Channel>) -> Result<(), Box<dyn Error>> {
755    let start = Instant::now();
756
757    let outbound = async_stream::stream! {
758        let mut interval = Interval::new_interval(Duration::from_secs(1));
759
760        while let Some(time) = interval.next().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 request = Request::new(outbound);
775    let response = client.route_chat(request).await?;
776    let mut inbound = response.into_inner();
777
778    while let Some(note) = inbound.message().await? {
779        println!("NOTE = {:?}", note);
780    }
781
782    Ok(())
783}
784```
785In this case, we use the [async-stream] crate to generate our outbound stream, yielding
786`RouteNote` values in one second intervals. We then iterate over the stream returned by
787the server, printing each value in the stream.
788
789## Try it out!
790
791### Run the server
792```shell
793$ cargo run --bin routeguide-server
794```
795
796### Run the client
797```shell
798$ cargo run --bin routeguide-client
799```
800
801## Appendix
802
803<a name="tonic-build"></a>
804### tonic_build configuration
805
806Tonic's default code generation configuration is convenient for self contained examples and small
807projects. However, there are some cases when we need a slightly different workflow. For example:
808
809- When building rust clients and servers in different crates.
810- When building a rust client or server (or both) as part of a larger, multi-language project.
811- When we want editor support for the generate code and our editor does not index the generated
812files in the default location.
813
814More generally, whenever we want to keep our `.proto` definitions in a central place and generate
815code for different crates or different languages, the default configuration is not enough.
816
817Luckily, `tonic_build` can be configured to fit whatever workflow we need. Here are just two
818possibilities:
819
8201)  We can keep our `.proto` definitions in a separate crate and generate our code on demand, as
821opposed to at build time, placing the resulting modules wherever we need them.
822
823`main.rs`
824
825```rust
826fn main() {
827    tonic_build::configure()
828        .build_client(false)
829        .out_dir("another_crate/src/pb")
830        .compile(&["path/my_proto.proto"], &["path"])
831        .expect("failed to compile protos");
832}
833```
834
835On `cargo run`, this will generate code for the client only, and place the resulting file in
836`another_crate/src/pb`.
837
8382) Similarly, we could also keep the `.proto` definitions in a separate crate and then use that
839crate as a direct dependency wherever we need it.
840
841