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