xref: /tonic/examples/routeguide-tutorial.md (revision a562a3ce)
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
50Run the server
51```shell
52$ cargo run --bin routeguide-server
53```
54
55In a separate shell, run the client
56```shell
57$ cargo run --bin routeguide-client
58```
59
60You should see some logging output flying past really quickly on both terminal windows. On the
61shell where you ran the client binary, you should see the output of the bidirectional streaming rpc,
62printing 1 line per second:
63
64```
65NOTE = RouteNote { location: Some(Point { latitude: 409146139, longitude: -746188906 }), message: "at 1.000319208s" }
66```
67
68If you scroll up you should see the output of the other 3 request types: simple rpc, server-side
69streaming and client-side streaming.
70
71
72## Project setup
73
74We will develop our example from scratch in a new crate:
75
76```shell
77$ cargo new routeguide
78$ cd routeguide
79```
80
81
82## Defining the service
83
84Our first step is to define the gRPC *service* and the method *request* and *response* types using
85[protocol buffers]. We will keep our `.proto` files in a directory in our crate's root.
86Note that Tonic does not really care where our `.proto` definitions live. We will see how to use
87different [code generation configuration](#tonic-build) later in the tutorial.
88
89```shell
90$ mkdir proto && touch proto/route_guide.proto
91```
92
93You can see the complete `.proto` file in
94[examples/proto/routeguide/route_guide.proto][routeguide-proto].
95
96To define a service, you specify a named `service` in your `.proto` file:
97
98```proto
99service RouteGuide {
100   ...
101}
102```
103
104Then you define `rpc` methods inside your service definition, specifying their request and response
105types. gRPC lets you define four kinds of service method, all of which are used in the `RouteGuide`
106service:
107
108- A *simple RPC* where the client sends a request to the server and waits for a response to come
109back, just like a normal function call.
110```proto
111   // Obtains the feature at a given position.
112   rpc GetFeature(Point) returns (Feature) {}
113```
114
115- A *server-side streaming RPC* where the client sends a request to the server and gets a stream
116to read a sequence of messages back. The client reads from the returned stream until there are
117no more messages. As you can see in our example, you specify a server-side streaming method by
118placing the `stream` keyword before the *response* type.
119```proto
120  // Obtains the Features available within the given Rectangle.  Results are
121  // streamed rather than returned at once (e.g. in a response message with a
122  // repeated field), as the rectangle may cover a large area and contain a
123  // huge number of features.
124  rpc ListFeatures(Rectangle) returns (stream Feature) {}
125```
126
127- A *client-side streaming RPC* where the client writes a sequence of messages and sends them to
128the server. Once the client has finished writing the messages, it waits for the server to read them
129all and return its response. You specify a client-side streaming method by placing the `stream`
130keyword before the *request* type.
131```proto
132  // Accepts a stream of Points on a route being traversed, returning a
133  // RouteSummary when traversal is completed.
134  rpc RecordRoute(stream Point) returns (RouteSummary) {}
135```
136
137- A *bidirectional streaming RPC* where both sides send a sequence of messages. The two streams
138operate independently, so clients and servers can read and write in whatever
139order they like: for example, the server could wait to receive all the client messages before
140writing its responses, or it could alternately read a message then write a message, or some other
141combination of reads and writes. The order of messages in each stream is preserved. You specify
142this type of method by placing the `stream` keyword before both the request and the response.
143```proto
144  // Accepts a stream of RouteNotes sent while a route is being traversed,
145  // while receiving other RouteNotes (e.g. from other users).
146  rpc RouteChat(stream RouteNote) returns (stream RouteNote) {}
147```
148
149Our `.proto` file also contains protocol buffer message type definitions for all the request and
150response types used in our service methods - for example, here's the `Point` message type:
151```proto
152// Points are represented as latitude-longitude pairs in the E7 representation
153// (degrees multiplied by 10**7 and rounded to the nearest integer).
154// Latitudes should be in the range +/- 90 degrees and longitude should be in
155// the range +/- 180 degrees (inclusive).
156message Point {
157  int32 latitude = 1;
158  int32 longitude = 2;
159}
160```
161
162[routeguide-proto]: https://github.com/hyperium/tonic/blob/master/examples/proto/routeguide/route_guide.proto
163
164## Generating client and server code
165
166Tonic can be configured to generate code as part cargo's normal build process. This is very
167convenient because once we've set everything up, there is no extra step to keep the generated code
168and our `.proto` definitions in sync.
169
170Behind the scenes, Tonic uses [PROST!] to handle protocol buffer serialization and code
171generation.
172
173Edit `Cargo.toml` and add all the dependencies we'll need for this example:
174
175```toml
176[dependencies]
177tonic = "0.8"
178prost = "0.11"
179futures-core = "0.3"
180futures-util = "0.3"
181tokio = { version = "1.0", features = ["rt-multi-thread", "macros", "sync", "time"] }
182tokio-stream = "0.1"
183
184async-stream = "0.2"
185serde = { version = "1.0", features = ["derive"] }
186serde_json = "1.0"
187rand = "0.7"
188
189[build-dependencies]
190tonic-build = "0.8"
191```
192
193Create a `build.rs` file at the root of your crate:
194
195```rust
196fn main() {
197    tonic_build::compile_protos("proto/route_guide.proto")
198        .unwrap_or_else(|e| panic!("Failed to compile protos {:?}", e));
199}
200```
201
202```shell
203$ cargo build
204```
205
206That's it. The generated code contains:
207
208- Struct definitions for message types `Point`, `Rectangle`, `Feature`, `RouteNote`, `RouteSummary`.
209- A service trait we'll need to implement: `route_guide_server::RouteGuide`.
210- A client type we'll use to call the server: `route_guide_client::RouteGuideClient<T>`.
211
212If your are curious as to where the generated files are, keep reading. The mystery will be revealed
213soon! We can now move on to the fun part.
214
215[PROST!]: https://github.com/danburkert/prost
216
217## Creating the server
218
219First let's look at how we create a `RouteGuide` server. If you're only interested in creating gRPC
220clients, you can skip this section and go straight to [Creating the client](#client)
221(though you might find it interesting anyway!).
222
223There are two parts to making our `RouteGuide` service do its job:
224
225- Implementing the service trait generated from our service definition.
226- Running a gRPC server to listen for requests from clients.
227
228You can find our example `RouteGuide` server in
229[examples/src/routeguide/server.rs][routeguide-server].
230
231[routeguide-server]: https://github.com/hyperium/tonic/blob/master/examples/src/routeguide/server.rs
232
233### Implementing the RouteGuide server trait
234
235We can start by defining a struct to represent our service, we can do this on `main.rs` for now:
236
237```rust
238#[derive(Debug)]
239struct RouteGuideService;
240```
241
242Next, we need to implement the `route_guide_server::RouteGuide` trait that is generated in our build step.
243The generated code is placed inside our target directory, in a location defined by the `OUT_DIR`
244environment variable that is set by cargo. For our example, this means you can find the generated
245code in a path similar to `target/debug/build/routeguide/out/routeguide.rs`.
246
247You can learn more about `build.rs` and the `OUT_DIR` environment variable in the [cargo book].
248
249We can use Tonic's `include_proto` macro to bring the generated code into scope:
250
251```rust
252pub mod routeguide {
253    tonic::include_proto!("routeguide");
254}
255
256use routeguide::route_guide_server::{RouteGuide, RouteGuideServer};
257use routeguide::{Feature, Point, Rectangle, RouteNote, RouteSummary};
258```
259
260**Note**: The token passed to the `include_proto` macro (in our case "routeguide") is the name of
261the package declared in our `.proto` file, not a filename, e.g "routeguide.rs".
262
263With this in place, we can stub out our service implementation:
264
265```rust
266use futures_core::Stream;
267use std::pin::Pin;
268use std::sync::Arc;
269use tokio::sync::mpsc;
270use tonic::{Request, Response, Status};
271use tokio_stream::wrappers::ReceiverStream;
272```
273
274```rust
275#[tonic::async_trait]
276impl RouteGuide for RouteGuideService {
277    async fn get_feature(&self, _request: Request<Point>) -> Result<Response<Feature>, Status> {
278        unimplemented!()
279    }
280
281    type ListFeaturesStream = ReceiverStream<Result<Feature, Status>>;
282
283    async fn list_features(
284        &self,
285        _request: Request<Rectangle>,
286    ) -> Result<Response<Self::ListFeaturesStream>, Status> {
287        unimplemented!()
288    }
289
290    async fn record_route(
291        &self,
292        _request: Request<tonic::Streaming<Point>>,
293    ) -> Result<Response<RouteSummary>, Status> {
294        unimplemented!()
295    }
296
297    type RouteChatStream = Pin<Box<dyn Stream<Item = Result<RouteNote, Status>> + Send  + 'static>>;
298
299    async fn route_chat(
300        &self,
301        _request: Request<tonic::Streaming<RouteNote>>,
302    ) -> Result<Response<Self::RouteChatStream>, Status> {
303        unimplemented!()
304    }
305}
306```
307
308**Note**: The `tonic::async_trait` attribute macro adds support for async functions in traits. It
309uses [async-trait] internally. You can learn more about `async fn` in traits in the [async book].
310
311
312[cargo book]: https://doc.rust-lang.org/cargo/reference/environment-variables.html#environment-variables-cargo-sets-for-build-scripts
313[async-trait]: https://github.com/dtolnay/async-trait
314[async book]: https://rust-lang.github.io/async-book/07_workarounds/05_async_in_traits.html
315
316### Server state
317Our service needs access to an immutable list of features. When the server starts, we are going to
318deserialize them from a json file and keep them around as our only piece of shared state:
319
320```rust
321#[derive(Debug)]
322pub struct RouteGuideService {
323    features: Arc<Vec<Feature>>,
324}
325```
326
327Create the json data file and a helper module to read and deserialize our features.
328
329```shell
330$ mkdir data && touch data/route_guide_db.json
331$ touch src/data.rs
332```
333
334You can find our example json data in [examples/data/route_guide_db.json][route-guide-db] and
335the corresponding `data` module to load and deserialize it in
336[examples/routeguide/data.rs][data-module].
337
338**Note:** If you are following along, you'll need to change the data file's path  from
339`examples/data/route_guide_db.json` to `data/route_guide_db.json`.
340
341Next, we need to implement `Hash` and `Eq` for `Point`, so we can use point values as map keys:
342
343```rust
344use std::hash::{Hasher, Hash};
345```
346
347```rust
348impl Hash for Point {
349    fn hash<H>(&self, state: &mut H)
350    where
351        H: Hasher,
352    {
353        self.latitude.hash(state);
354        self.longitude.hash(state);
355    }
356}
357
358impl Eq for Point {}
359
360```
361
362Lastly, we need implement two helper functions: `in_range` and `calc_distance`. We'll use them
363when performing feature lookups. You can find them in
364[examples/src/routeguide/server.rs][in-range-fn].
365
366[route-guide-db]: https://github.com/hyperium/tonic/blob/master/examples/data/route_guide_db.json
367[data-module]: https://github.com/hyperium/tonic/blob/master/examples/src/routeguide/data.rs
368[in-range-fn]: https://github.com/hyperium/tonic/blob/master/examples/src/routeguide/server.rs#L174
369
370#### Request and Response types
371All our service methods receive a `tonic::Request<T>` and return a
372`Result<tonic::Response<T>, tonic::Status>`. The concrete type of `T` depends on how our methods
373are declared in our *service* `.proto` definition. It can be either:
374
375- A single value, e.g `Point`, `Rectangle`, or even a message type that includes a repeated field.
376- A stream of values, e.g. `impl Stream<Item = Result<Feature, tonic::Status>>`.
377
378#### Simple RPC
379Let's look at the simplest method first, `get_feature`, which just gets a `tonic::Request<Point>`
380from the client and tries to find a feature at the given `Point`. If no feature is found, it returns
381an empty one.
382
383```rust
384async fn get_feature(&self, request: Request<Point>) -> Result<Response<Feature>, Status> {
385    for feature in &self.features[..] {
386        if feature.location.as_ref() == Some(request.get_ref()) {
387            return Ok(Response::new(feature.clone()));
388        }
389    }
390
391    Ok(Response::new(Feature::default()))
392}
393```
394
395
396#### Server-side streaming RPC
397Now let's look at one of our streaming RPCs. `list_features` is a server-side streaming RPC, so we
398need to send back multiple `Feature`s to our client.
399
400```rust
401type ListFeaturesStream = ReceiverStream<Result<Feature, Status>>;
402
403async fn list_features(
404    &self,
405    request: Request<Rectangle>,
406) -> Result<Response<Self::ListFeaturesStream>, Status> {
407    let (mut tx, rx) = mpsc::channel(4);
408    let features = self.features.clone();
409
410    tokio::spawn(async move {
411        for feature in &features[..] {
412            if in_range(feature.location.as_ref().unwrap(), request.get_ref()) {
413                tx.send(Ok(feature.clone())).await.unwrap();
414            }
415        }
416    });
417
418    Ok(Response::new(ReceiverStream::new(rx)))
419}
420```
421
422Like `get_feature`, `list_features`'s input is a single message, a `Rectangle` in this
423case. This time, however, we need to return a stream of values, rather than a single one.
424We create a channel and spawn a new asynchronous task where we perform a lookup, sending
425the features that satisfy our constraints into the channel.
426
427The `Stream` half of the channel is returned to the caller, wrapped in a `tonic::Response`.
428
429
430#### Client-side streaming RPC
431Now let's look at something a little more complicated: the client-side streaming method
432`record_route`, where we get a stream of `Point`s from the client and return a single `RouteSummary`
433with information about their trip. As you can see, this time the method receives a
434`tonic::Request<tonic::Streaming<Point>>`.
435
436```rust
437use std::time::Instant;
438use futures_util::StreamExt;
439```
440
441```rust
442async fn record_route(
443    &self,
444    request: Request<tonic::Streaming<Point>>,
445) -> Result<Response<RouteSummary>, Status> {
446    let mut stream = request.into_inner();
447
448    let mut summary = RouteSummary::default();
449    let mut last_point = None;
450    let now = Instant::now();
451
452    while let Some(point) = stream.next().await {
453        let point = point?;
454        summary.point_count += 1;
455
456        for feature in &self.features[..] {
457            if feature.location.as_ref() == Some(&point) {
458                summary.feature_count += 1;
459            }
460        }
461
462        if let Some(ref last_point) = last_point {
463            summary.distance += calc_distance(last_point, &point);
464        }
465
466        last_point = Some(point);
467    }
468
469    summary.elapsed_time = now.elapsed().as_secs() as i32;
470
471    Ok(Response::new(summary))
472}
473```
474
475`record_route` is conceptually simple: we get a stream of `Points` and fold it into a `RouteSummary`.
476In other words, we build a summary value as we process each `Point` in our stream, one by one.
477When there are no more `Points` in our stream, we return the `RouteSummary` wrapped in a
478`tonic::Response`.
479
480#### Bidirectional streaming RPC
481Finally, let's look at our bidirectional streaming RPC `route_chat`, which receives a stream
482of `RouteNote`s and returns  a stream of `RouteNote`s.
483
484```rust
485use std::collections::HashMap;
486```
487
488```rust
489type RouteChatStream =
490    Pin<Box<dyn Stream<Item = Result<RouteNote, Status>> + Send  + 'static>>;
491
492
493async fn route_chat(
494    &self,
495    request: Request<tonic::Streaming<RouteNote>>,
496) -> Result<Response<Self::RouteChatStream>, Status> {
497    let mut notes = HashMap::new();
498    let mut stream = request.into_inner();
499
500    let output = async_stream::try_stream! {
501        while let Some(note) = stream.next().await {
502            let note = note?;
503
504            let location = note.location.clone().unwrap();
505
506            let location_notes = notes.entry(location).or_insert(vec![]);
507            location_notes.push(note);
508
509            for note in location_notes {
510                yield note.clone();
511            }
512        }
513    };
514
515    Ok(Response::new(Box::pin(output)
516        as Self::RouteChatStream))
517
518}
519```
520
521`route_chat` uses the [async-stream] crate to perform an asynchronous transformation
522from one (input) stream to another (output) stream. As the input is processed, each value is
523inserted into the notes map, yielding a clone of the original `RouteNote`. The resulting stream
524is then returned to the caller. Neat.
525
526**Note**: The funky `as` cast is needed due to a limitation in the rust compiler. This is expected
527to be fixed soon.
528
529[async-stream]: https://github.com/tokio-rs/async-stream
530
531### Starting the server
532
533Once we've implemented all our methods, we also need to start up a gRPC server so that clients can
534actually use our service. This is how our `main` function looks like:
535
536```rust
537mod data;
538use tonic::transport::Server;
539```
540
541```rust
542#[tokio::main]
543async fn main() -> Result<(), Box<dyn std::error::Error>> {
544    let addr = "[::1]:10000".parse().unwrap();
545
546    let route_guide = RouteGuideService {
547        features: Arc::new(data::load()),
548    };
549
550    let svc = RouteGuideServer::new(route_guide);
551
552    Server::builder().add_service(svc).serve(addr).await?;
553
554    Ok(())
555}
556```
557
558To handle requests, `Tonic` uses [Tower] and [hyper] internally. What this means,
559among other things, is that we have a flexible and composable stack we can build on top of. We can,
560for example, add an [interceptor][authentication-example] to process requests before they reach our service
561methods.
562
563
564[Tower]: https://github.com/tower-rs
565[hyper]: https://github.com/hyperium/hyper
566[authentication-example]: https://github.com/hyperium/tonic/blob/master/examples/src/authentication/server.rs#L56
567
568<a name="client"></a>
569## Creating the client
570
571In this section, we'll look at creating a Tonic client for our `RouteGuide` service. You can see our
572complete example client code in [examples/src/routeguide/client.rs][routeguide-client].
573
574Our crate will have two binary targets: `routeguide-client` and `routeguide-server`. We need to
575edit our `Cargo.toml` accordingly:
576
577```toml
578[[bin]]
579name = "routeguide-server"
580path = "src/server.rs"
581
582[[bin]]
583name = "routeguide-client"
584path = "src/client.rs"
585```
586
587Rename `main.rs` to `server.rs` and create a new file `client.rs`.
588
589```shell
590$ mv src/main.rs src/server.rs
591$ touch src/client.rs
592```
593
594To call service methods, we first need to create a gRPC *client* to communicate with the server. Like in the server
595case, we'll start by bringing the generated code into scope:
596
597```rust
598pub mod routeguide {
599    tonic::include_proto!("routeguide");
600}
601
602use routeguide::route_guide_client::RouteGuideClient;
603use routeguide::{Point, Rectangle, RouteNote};
604
605
606#[tokio::main]
607async fn main() -> Result<(), Box<dyn std::error::Error>> {
608    let mut client = RouteGuideClient::connect("http://[::1]:10000").await?;
609
610     Ok(())
611}
612```
613
614Same as in the server implementation, we start by bringing our generated code into scope. We then
615create a client in our main function, passing the server's full URL to `RouteGuideClient::connect`.
616Our client is now ready to make service calls. Note that `client` is mutable, this is because it
617needs to manage internal state.
618
619[routeguide-client]: https://github.com/hyperium/tonic/blob/master/examples/src/routeguide/client.rs
620
621
622### Calling service methods
623Now let's look at how we call our service methods. Note that in Tonic, RPCs are asynchronous,
624which means that RPC calls need to be `.await`ed.
625
626#### Simple RPC
627Calling the simple RPC `get_feature` is as straightforward as calling a local method:
628
629```rust
630use tonic::Request;
631```
632
633```rust
634let response = client
635    .get_feature(Request::new(Point {
636        latitude: 409146138,
637        longitude: -746188906,
638    }))
639    .await?;
640
641println!("RESPONSE = {:?}", response);
642```
643We call the `get_feature` client method, passing a single `Point` value wrapped in a
644`tonic::Request`. We get a `Result<tonic::Response<Feature>, tonic::Status>` back.
645
646#### Server-side streaming RPC
647Here's where we call the server-side streaming method `list_features`, which returns a stream of
648geographical `Feature`s.
649
650```rust
651use tonic::transport::Channel;
652use std::error::Error;
653```
654
655```rust
656async fn print_features(client: &mut RouteGuideClient<Channel>) -> Result<(), Box<dyn Error>> {
657    let rectangle = Rectangle {
658        lo: Some(Point {
659            latitude: 400000000,
660            longitude: -750000000,
661        }),
662        hi: Some(Point {
663            latitude: 420000000,
664            longitude: -730000000,
665        }),
666    };
667
668    let mut stream = client
669        .list_features(Request::new(rectangle))
670        .await?
671        .into_inner();
672
673    while let Some(feature) = stream.message().await? {
674        println!("NOTE = {:?}", feature);
675    }
676
677    Ok(())
678}
679```
680
681As in the simple RPC, we pass a single value request. However, instead of getting a
682single value back, we get a stream of `Features`.
683
684We use the the `message()` method from the `tonic::Streaming` struct to repeatedly read in the
685server's responses to a response protocol buffer object (in this case a `Feature`) until there are
686no more messages left in the stream.
687
688#### Client-side streaming RPC
689The client-side streaming method `record_route` takes a stream of `Point`s and returns a single
690`RouteSummary` value.
691
692```rust
693use rand::rngs::ThreadRng;
694use rand::Rng;
695use futures_util::stream;
696```
697
698```rust
699async fn run_record_route(client: &mut RouteGuideClient<Channel>) -> Result<(), Box<dyn Error>> {
700    let mut rng = rand::thread_rng();
701    let point_count: i32 = rng.gen_range(2..100);
702
703    let mut points = vec![];
704    for _ in 0..=point_count {
705        points.push(random_point(&mut rng))
706    }
707
708    println!("Traversing {} points", points.len());
709    let request = Request::new(stream::iter(points));
710
711    match client.record_route(request).await {
712        Ok(response) => println!("SUMMARY: {:?}", response.into_inner()),
713        Err(e) => println!("something went wrong: {:?}", e),
714    }
715
716    Ok(())
717}
718```
719
720```rust
721fn random_point(rng: &mut ThreadRng) -> Point {
722    let latitude = (rng.gen_range(0..180) - 90) * 10_000_000;
723    let longitude = (rng.gen_range(0..360) - 180) * 10_000_000;
724    Point {
725        latitude,
726        longitude,
727    }
728}
729```
730
731We build a vector of a random number of `Point` values (between 2 and 100) and then convert
732it into a `Stream` using the `futures::stream::iter` function. This is a cheap an easy way to get
733a stream suitable for passing into our service method. The resulting stream is then wrapped in a
734`tonic::Request`.
735
736
737#### Bidirectional streaming RPC
738
739Finally, let's look at our bidirectional streaming RPC. The `route_chat` method takes a stream
740of `RouteNotes` and returns either another stream of `RouteNotes` or an error.
741
742```rust
743use std::time::Duration;
744use tokio::time;
745```
746
747```rust
748async fn run_route_chat(client: &mut RouteGuideClient<Channel>) -> Result<(), Box<dyn Error>> {
749    let start = time::Instant::now();
750
751    let outbound = async_stream::stream! {
752        let mut interval = time::interval(Duration::from_secs(1));
753
754        while let time = interval.tick().await {
755            let elapsed = time.duration_since(start);
756            let note = RouteNote {
757                location: Some(Point {
758                    latitude: 409146138 + elapsed.as_secs() as i32,
759                    longitude: -746188906,
760                }),
761                message: format!("at {:?}", elapsed),
762            };
763
764            yield note;
765        }
766    };
767
768    let response = client.route_chat(Request::new(outbound)).await?;
769    let mut inbound = response.into_inner();
770
771    while let Some(note) = inbound.message().await? {
772        println!("NOTE = {:?}", note);
773    }
774
775    Ok(())
776}
777```
778In this case, we use the [async-stream] crate to generate our outbound stream, yielding
779`RouteNote` values in one second intervals. We then iterate over the stream returned by
780the server, printing each value in the stream.
781
782## Try it out!
783
784### Run the server
785```shell
786$ cargo run --bin routeguide-server
787```
788
789### Run the client
790```shell
791$ cargo run --bin routeguide-client
792```
793
794## Appendix
795
796<a name="tonic-build"></a>
797### tonic_build configuration
798
799Tonic's default code generation configuration is convenient for self contained examples and small
800projects. However, there are some cases when we need a slightly different workflow. For example:
801
802- When building rust clients and servers in different crates.
803- When building a rust client or server (or both) as part of a larger, multi-language project.
804- When we want editor support for the generate code and our editor does not index the generated
805files in the default location.
806
807More generally, whenever we want to keep our `.proto` definitions in a central place and generate
808code for different crates or different languages, the default configuration is not enough.
809
810Luckily, `tonic_build` can be configured to fit whatever workflow we need. Here are just two
811possibilities:
812
8131)  We can keep our `.proto` definitions in a separate crate and generate our code on demand, as
814opposed to at build time, placing the resulting modules wherever we need them.
815
816`main.rs`
817
818```rust
819fn main() {
820    tonic_build::configure()
821        .build_client(false)
822        .out_dir("another_crate/src/pb")
823        .compile(&["path/my_proto.proto"], &["path"])
824        .expect("failed to compile protos");
825}
826```
827
828On `cargo run`, this will generate code for the server only, and place the resulting file in
829`another_crate/src/pb`.
830
8312) Similarly, we could also keep the `.proto` definitions in a separate crate and then use that
832crate as a direct dependency wherever we need it.
833
834