1d9a481baSLucio Franco# gRPC Basics: Tonic 2d9a481baSLucio Franco 3d9a481baSLucio FrancoThis tutorial, adapted from [grpc-go], provides a basic introduction to working with gRPC 4d9a481baSLucio Francoand Tonic. By walking through this example you'll learn how to: 5d9a481baSLucio Franco 6d9a481baSLucio Franco- Define a service in a `.proto` file. 7d9a481baSLucio Franco- Generate server and client code. 8d9a481baSLucio Franco- Write a simple client and server for your service. 9d9a481baSLucio Franco 10d9a481baSLucio FrancoIt assumes you are familiar with [protocol buffers] and basic Rust. Note that the example in 11d9a481baSLucio Francothis tutorial uses the proto3 version of the protocol buffers language, you can find out more in the 12d9a481baSLucio Franco[proto3 language guide][proto3]. 13d9a481baSLucio Franco 14d9a481baSLucio Franco[grpc-go]: https://github.com/grpc/grpc-go/blob/master/examples/gotutorial.md 15d9a481baSLucio Franco[protocol buffers]: https://developers.google.com/protocol-buffers/docs/overview 16d9a481baSLucio Franco[proto3]: https://developers.google.com/protocol-buffers/docs/proto3 17d9a481baSLucio Franco 18d9a481baSLucio Franco## Why use gRPC? 19d9a481baSLucio Franco 20d9a481baSLucio FrancoOur example is a simple route mapping application that lets clients get information about features 21d9a481baSLucio Francoon their route, create a summary of their route, and exchange route information such as traffic 22d9a481baSLucio Francoupdates with the server and other clients. 23d9a481baSLucio Franco 24d9a481baSLucio FrancoWith gRPC we can define our service once in a `.proto` file and implement clients and servers in 25d9a481baSLucio Francoany of gRPC's supported languages, which in turn can be run in environments ranging from servers 26d9a481baSLucio Francoinside Google to your own tablet - all the complexity of communication between different languages 27d9a481baSLucio Francoand environments is handled for you by gRPC. We also get all the advantages of working with 28d9a481baSLucio Francoprotocol buffers, including efficient serialization, a simple IDL, and easy interface updating. 29d9a481baSLucio Franco 30d9a481baSLucio Franco## Prerequisites 31d9a481baSLucio Franco 32d9a481baSLucio FrancoTo run the sample code and walk through the tutorial, the only prerequisite is Rust itself. 33d9a481baSLucio Franco[rustup] is a convenient tool to install it, if you haven't already. 34d9a481baSLucio Franco 35d9a481baSLucio Franco[rustup]: https://rustup.rs 36d9a481baSLucio Franco 37d9a481baSLucio Franco## Running the example 38d9a481baSLucio Franco 39d9a481baSLucio FrancoClone or download Tonic's repository: 40d9a481baSLucio Franco 41d9a481baSLucio Franco```shell 42d9a481baSLucio Franco$ git clone https://github.com/hyperium/tonic.git 43d9a481baSLucio Franco``` 44d9a481baSLucio Franco 45d9a481baSLucio FrancoChange your current directory to Tonic's repository root: 46d9a481baSLucio Franco```shell 47d9a481baSLucio Franco$ cd tonic 48d9a481baSLucio Franco``` 49d9a481baSLucio Franco 50d9a481baSLucio FrancoRun the server 51d9a481baSLucio Franco```shell 52d9a481baSLucio Franco$ cargo run --bin routeguide-server 53d9a481baSLucio Franco``` 54d9a481baSLucio Franco 55d9a481baSLucio FrancoIn a separate shell, run the client 56d9a481baSLucio Franco```shell 57d9a481baSLucio Franco$ cargo run --bin routeguide-client 58d9a481baSLucio Franco``` 59d9a481baSLucio Franco 60d9a481baSLucio FrancoYou should see some logging output flying past really quickly on both terminal windows. On the 61d9a481baSLucio Francoshell where you ran the client binary, you should see the output of the bidirectional streaming rpc, 62d9a481baSLucio Francoprinting 1 line per second: 63d9a481baSLucio Franco 64faa26ac1SJuan Alvarez``` 65d9a481baSLucio FrancoNOTE = RouteNote { location: Some(Point { latitude: 409146139, longitude: -746188906 }), message: "at 1.000319208s" } 66faa26ac1SJuan Alvarez``` 67d9a481baSLucio Franco 68d9a481baSLucio FrancoIf you scroll up you should see the output of the other 3 request types: simple rpc, server-side 69d9a481baSLucio Francostreaming and client-side streaming. 70d9a481baSLucio Franco 71d9a481baSLucio Franco 72d9a481baSLucio Franco## Project setup 73d9a481baSLucio Franco 74d9a481baSLucio FrancoWe will develop our example from scratch in a new crate: 75d9a481baSLucio Franco 76d9a481baSLucio Franco```shell 77d9a481baSLucio Franco$ cargo new routeguide 78d9a481baSLucio Franco$ cd routeguide 79d9a481baSLucio Franco``` 80d9a481baSLucio Franco 81d9a481baSLucio Franco 82d9a481baSLucio Franco## Defining the service 83d9a481baSLucio Franco 84d9a481baSLucio FrancoOur first step is to define the gRPC *service* and the method *request* and *response* types using 85d9a481baSLucio Franco[protocol buffers]. We will keep our `.proto` files in a directory in our crate's root. 86d9a481baSLucio FrancoNote that Tonic does not really care where our `.proto` definitions live. We will see how to use 87d9a481baSLucio Francodifferent [code generation configuration](#tonic-build) later in the tutorial. 88d9a481baSLucio Franco 89d9a481baSLucio Franco```shell 90d9a481baSLucio Franco$ mkdir proto && touch proto/route_guide.proto 91d9a481baSLucio Franco``` 92d9a481baSLucio Franco 93d9a481baSLucio FrancoYou can see the complete `.proto` file in 946566b237SFletcher Nichol[examples/proto/routeguide/route_guide.proto][routeguide-proto]. 95d9a481baSLucio Franco 96d9a481baSLucio FrancoTo define a service, you specify a named `service` in your `.proto` file: 97d9a481baSLucio Franco 98d9a481baSLucio Franco```proto 99d9a481baSLucio Francoservice RouteGuide { 100d9a481baSLucio Franco ... 101d9a481baSLucio Franco} 102d9a481baSLucio Franco``` 103d9a481baSLucio Franco 104d9a481baSLucio FrancoThen you define `rpc` methods inside your service definition, specifying their request and response 105d9a481baSLucio Francotypes. gRPC lets you define four kinds of service method, all of which are used in the `RouteGuide` 106d9a481baSLucio Francoservice: 107d9a481baSLucio Franco 108d9a481baSLucio Franco- A *simple RPC* where the client sends a request to the server and waits for a response to come 109d9a481baSLucio Francoback, just like a normal function call. 110d9a481baSLucio Franco```proto 111d9a481baSLucio Franco // Obtains the feature at a given position. 112d9a481baSLucio Franco rpc GetFeature(Point) returns (Feature) {} 113d9a481baSLucio Franco``` 114d9a481baSLucio Franco 115d9a481baSLucio Franco- A *server-side streaming RPC* where the client sends a request to the server and gets a stream 116d9a481baSLucio Francoto read a sequence of messages back. The client reads from the returned stream until there are 117d9a481baSLucio Francono more messages. As you can see in our example, you specify a server-side streaming method by 118d9a481baSLucio Francoplacing the `stream` keyword before the *response* type. 119d9a481baSLucio Franco```proto 120d9a481baSLucio Franco // Obtains the Features available within the given Rectangle. Results are 121d9a481baSLucio Franco // streamed rather than returned at once (e.g. in a response message with a 122d9a481baSLucio Franco // repeated field), as the rectangle may cover a large area and contain a 123d9a481baSLucio Franco // huge number of features. 124d9a481baSLucio Franco rpc ListFeatures(Rectangle) returns (stream Feature) {} 125d9a481baSLucio Franco``` 126d9a481baSLucio Franco 127d9a481baSLucio Franco- A *client-side streaming RPC* where the client writes a sequence of messages and sends them to 128d9a481baSLucio Francothe server. Once the client has finished writing the messages, it waits for the server to read them 129d9a481baSLucio Francoall and return its response. You specify a client-side streaming method by placing the `stream` 130d9a481baSLucio Francokeyword before the *request* type. 131d9a481baSLucio Franco```proto 132d9a481baSLucio Franco // Accepts a stream of Points on a route being traversed, returning a 133d9a481baSLucio Franco // RouteSummary when traversal is completed. 134d9a481baSLucio Franco rpc RecordRoute(stream Point) returns (RouteSummary) {} 135d9a481baSLucio Franco``` 136d9a481baSLucio Franco 137d9a481baSLucio Franco- A *bidirectional streaming RPC* where both sides send a sequence of messages. The two streams 138d9a481baSLucio Francooperate independently, so clients and servers can read and write in whatever 139d9a481baSLucio Francoorder they like: for example, the server could wait to receive all the client messages before 140d9a481baSLucio Francowriting its responses, or it could alternately read a message then write a message, or some other 141d9a481baSLucio Francocombination of reads and writes. The order of messages in each stream is preserved. You specify 142d9a481baSLucio Francothis type of method by placing the `stream` keyword before both the request and the response. 143d9a481baSLucio Franco```proto 144d9a481baSLucio Franco // Accepts a stream of RouteNotes sent while a route is being traversed, 145d9a481baSLucio Franco // while receiving other RouteNotes (e.g. from other users). 146d9a481baSLucio Franco rpc RouteChat(stream RouteNote) returns (stream RouteNote) {} 147d9a481baSLucio Franco``` 148d9a481baSLucio Franco 149d9a481baSLucio FrancoOur `.proto` file also contains protocol buffer message type definitions for all the request and 150d9a481baSLucio Francoresponse types used in our service methods - for example, here's the `Point` message type: 151d9a481baSLucio Franco```proto 152d9a481baSLucio Franco// Points are represented as latitude-longitude pairs in the E7 representation 153d9a481baSLucio Franco// (degrees multiplied by 10**7 and rounded to the nearest integer). 154d9a481baSLucio Franco// Latitudes should be in the range +/- 90 degrees and longitude should be in 155d9a481baSLucio Franco// the range +/- 180 degrees (inclusive). 156d9a481baSLucio Francomessage Point { 157d9a481baSLucio Franco int32 latitude = 1; 158d9a481baSLucio Franco int32 longitude = 2; 159d9a481baSLucio Franco} 160d9a481baSLucio Franco``` 161d9a481baSLucio Franco 1626566b237SFletcher Nichol[routeguide-proto]: https://github.com/hyperium/tonic/blob/master/examples/proto/routeguide/route_guide.proto 163d9a481baSLucio Franco 164d9a481baSLucio Franco## Generating client and server code 165d9a481baSLucio Franco 166d9a481baSLucio FrancoTonic can be configured to generate code as part cargo's normal build process. This is very 167d9a481baSLucio Francoconvenient because once we've set everything up, there is no extra step to keep the generated code 168d9a481baSLucio Francoand our `.proto` definitions in sync. 169d9a481baSLucio Franco 170d9a481baSLucio FrancoBehind the scenes, Tonic uses [PROST!] to handle protocol buffer serialization and code 171d9a481baSLucio Francogeneration. 172d9a481baSLucio Franco 173d9a481baSLucio FrancoEdit `Cargo.toml` and add all the dependencies we'll need for this example: 174d9a481baSLucio Franco 175d9a481baSLucio Franco```toml 176d9a481baSLucio Franco[dependencies] 177eb66b914SEaswar Swaminathantonic = "*" 1784aad5afaSLukeprost = "0.13" 179cb9ed185SDaniel Mangumtokio = { version = "1.0", features = ["rt-multi-thread", "macros", "sync", "time"] } 180db22c356SJunichi Sugiuratokio-stream = "0.1" 1814e5c6c8fSJuan Alvarez 1824e5c6c8fSJuan Alvarezasync-stream = "0.2" 183d9a481baSLucio Francoserde = { version = "1.0", features = ["derive"] } 184d9a481baSLucio Francoserde_json = "1.0" 18537bef0b2Stsingwongrand = "0.8" 186d9a481baSLucio Franco 187d9a481baSLucio Franco[build-dependencies] 188eb66b914SEaswar Swaminathantonic-build = "*" 189d9a481baSLucio Franco``` 190d9a481baSLucio Franco 191d9a481baSLucio FrancoCreate a `build.rs` file at the root of your crate: 192d9a481baSLucio Franco 193d9a481baSLucio Franco```rust 194d9a481baSLucio Francofn main() { 1959e7d35a2SGovardhan G D tonic_build::compile_protos("proto/route_guide.proto") 196d9a481baSLucio Franco .unwrap_or_else(|e| panic!("Failed to compile protos {:?}", e)); 197d9a481baSLucio Franco} 198d9a481baSLucio Franco``` 199d9a481baSLucio Franco 200d9a481baSLucio Franco```shell 201d9a481baSLucio Franco$ cargo build 202d9a481baSLucio Franco``` 203d9a481baSLucio Franco 204d9a481baSLucio FrancoThat's it. The generated code contains: 205d9a481baSLucio Franco 206d9a481baSLucio Franco- Struct definitions for message types `Point`, `Rectangle`, `Feature`, `RouteNote`, `RouteSummary`. 2074e5c6c8fSJuan Alvarez- A service trait we'll need to implement: `route_guide_server::RouteGuide`. 2084e5c6c8fSJuan Alvarez- A client type we'll use to call the server: `route_guide_client::RouteGuideClient<T>`. 209d9a481baSLucio Franco 210d9a481baSLucio FrancoIf your are curious as to where the generated files are, keep reading. The mystery will be revealed 211d9a481baSLucio Francosoon! We can now move on to the fun part. 212d9a481baSLucio Franco 213d9a481baSLucio Franco[PROST!]: https://github.com/danburkert/prost 214d9a481baSLucio Franco 215d9a481baSLucio Franco## Creating the server 216d9a481baSLucio Franco 217d9a481baSLucio FrancoFirst let's look at how we create a `RouteGuide` server. If you're only interested in creating gRPC 218d9a481baSLucio Francoclients, you can skip this section and go straight to [Creating the client](#client) 219d9a481baSLucio Franco(though you might find it interesting anyway!). 220d9a481baSLucio Franco 221d9a481baSLucio FrancoThere are two parts to making our `RouteGuide` service do its job: 222d9a481baSLucio Franco 223d9a481baSLucio Franco- Implementing the service trait generated from our service definition. 224d9a481baSLucio Franco- Running a gRPC server to listen for requests from clients. 225d9a481baSLucio Franco 226d9a481baSLucio FrancoYou can find our example `RouteGuide` server in 2276566b237SFletcher Nichol[examples/src/routeguide/server.rs][routeguide-server]. 228d9a481baSLucio Franco 2296566b237SFletcher Nichol[routeguide-server]: https://github.com/hyperium/tonic/blob/master/examples/src/routeguide/server.rs 230d9a481baSLucio Franco 2314e5c6c8fSJuan Alvarez### Implementing the RouteGuide server trait 232d9a481baSLucio Franco 233d9a481baSLucio FrancoWe can start by defining a struct to represent our service, we can do this on `main.rs` for now: 234d9a481baSLucio Franco 235d9a481baSLucio Franco```rust 236d9a481baSLucio Franco#[derive(Debug)] 2374e5c6c8fSJuan Alvarezstruct RouteGuideService; 238d9a481baSLucio Franco``` 239d9a481baSLucio Franco 2404e5c6c8fSJuan AlvarezNext, we need to implement the `route_guide_server::RouteGuide` trait that is generated in our build step. 241d9a481baSLucio FrancoThe generated code is placed inside our target directory, in a location defined by the `OUT_DIR` 242d9a481baSLucio Francoenvironment variable that is set by cargo. For our example, this means you can find the generated 243d9a481baSLucio Francocode in a path similar to `target/debug/build/routeguide/out/routeguide.rs`. 244d9a481baSLucio Franco 245d9a481baSLucio FrancoYou can learn more about `build.rs` and the `OUT_DIR` environment variable in the [cargo book]. 246d9a481baSLucio Franco 247d9a481baSLucio FrancoWe can use Tonic's `include_proto` macro to bring the generated code into scope: 248d9a481baSLucio Franco 249d9a481baSLucio Franco```rust 250d9a481baSLucio Francopub mod routeguide { 251d9a481baSLucio Franco tonic::include_proto!("routeguide"); 252d9a481baSLucio Franco} 253d9a481baSLucio Franco 254faa26ac1SJuan Alvarezuse routeguide::route_guide_server::{RouteGuide, RouteGuideServer}; 2554e5c6c8fSJuan Alvarezuse routeguide::{Feature, Point, Rectangle, RouteNote, RouteSummary}; 256d9a481baSLucio Franco``` 257d9a481baSLucio Franco 258d9a481baSLucio Franco**Note**: The token passed to the `include_proto` macro (in our case "routeguide") is the name of 259ddab65edSJerboas86the package declared in our `.proto` file, not a filename, e.g "routeguide.rs". 260d9a481baSLucio Franco 261d9a481baSLucio FrancoWith this in place, we can stub out our service implementation: 262d9a481baSLucio Franco 263d9a481baSLucio Franco```rust 264faa26ac1SJuan Alvarezuse std::pin::Pin; 265faa26ac1SJuan Alvarezuse std::sync::Arc; 2664e5c6c8fSJuan Alvarezuse tokio::sync::mpsc; 2674e5c6c8fSJuan Alvarezuse tonic::{Request, Response, Status}; 26819e5f396Stottotouse tokio_stream::{wrappers::ReceiverStream, Stream}; 269d9a481baSLucio Franco``` 270d9a481baSLucio Franco 271d9a481baSLucio Franco```rust 272d9a481baSLucio Franco#[tonic::async_trait] 273faa26ac1SJuan Alvarezimpl RouteGuide for RouteGuideService { 274d9a481baSLucio Franco async fn get_feature(&self, _request: Request<Point>) -> Result<Response<Feature>, Status> { 275d9a481baSLucio Franco unimplemented!() 276d9a481baSLucio Franco } 277d9a481baSLucio Franco 278db22c356SJunichi Sugiura type ListFeaturesStream = ReceiverStream<Result<Feature, Status>>; 279d9a481baSLucio Franco 280d9a481baSLucio Franco async fn list_features( 281d9a481baSLucio Franco &self, 282d9a481baSLucio Franco _request: Request<Rectangle>, 283d9a481baSLucio Franco ) -> Result<Response<Self::ListFeaturesStream>, Status> { 284d9a481baSLucio Franco unimplemented!() 285d9a481baSLucio Franco } 286d9a481baSLucio Franco 287d9a481baSLucio Franco async fn record_route( 288d9a481baSLucio Franco &self, 289d9a481baSLucio Franco _request: Request<tonic::Streaming<Point>>, 290d9a481baSLucio Franco ) -> Result<Response<RouteSummary>, Status> { 291d9a481baSLucio Franco unimplemented!() 292d9a481baSLucio Franco } 293d9a481baSLucio Franco 29423c1392fSLucio Franco type RouteChatStream = Pin<Box<dyn Stream<Item = Result<RouteNote, Status>> + Send + 'static>>; 295d9a481baSLucio Franco 296d9a481baSLucio Franco async fn route_chat( 297d9a481baSLucio Franco &self, 298d9a481baSLucio Franco _request: Request<tonic::Streaming<RouteNote>>, 299d9a481baSLucio Franco ) -> Result<Response<Self::RouteChatStream>, Status> { 300d9a481baSLucio Franco unimplemented!() 301d9a481baSLucio Franco } 302d9a481baSLucio Franco} 303d9a481baSLucio Franco``` 304d9a481baSLucio Franco 305d9a481baSLucio Franco**Note**: The `tonic::async_trait` attribute macro adds support for async functions in traits. It 306d9a481baSLucio Francouses [async-trait] internally. You can learn more about `async fn` in traits in the [async book]. 307d9a481baSLucio Franco 308d9a481baSLucio Franco 309d9a481baSLucio Franco[cargo book]: https://doc.rust-lang.org/cargo/reference/environment-variables.html#environment-variables-cargo-sets-for-build-scripts 310d9a481baSLucio Franco[async-trait]: https://github.com/dtolnay/async-trait 3113c2a4a28SMichael-J-Ward[async book]: https://rust-lang.github.io/async-book/07_workarounds/05_async_in_traits.html 312d9a481baSLucio Franco 313d9a481baSLucio Franco### Server state 314d9a481baSLucio FrancoOur service needs access to an immutable list of features. When the server starts, we are going to 315d9a481baSLucio Francodeserialize them from a json file and keep them around as our only piece of shared state: 316d9a481baSLucio Franco 317d9a481baSLucio Franco```rust 318d9a481baSLucio Franco#[derive(Debug)] 3194e5c6c8fSJuan Alvarezpub struct RouteGuideService { 320d9a481baSLucio Franco features: Arc<Vec<Feature>>, 321d9a481baSLucio Franco} 322d9a481baSLucio Franco``` 323d9a481baSLucio Franco 324d9a481baSLucio FrancoCreate the json data file and a helper module to read and deserialize our features. 325d9a481baSLucio Franco 326d9a481baSLucio Franco```shell 327d9a481baSLucio Franco$ mkdir data && touch data/route_guide_db.json 328d9a481baSLucio Franco$ touch src/data.rs 329d9a481baSLucio Franco``` 330d9a481baSLucio Franco 3316566b237SFletcher NicholYou can find our example json data in [examples/data/route_guide_db.json][route-guide-db] and 332d9a481baSLucio Francothe corresponding `data` module to load and deserialize it in 3336566b237SFletcher Nichol[examples/routeguide/data.rs][data-module]. 334d9a481baSLucio Franco 335d9a481baSLucio Franco**Note:** If you are following along, you'll need to change the data file's path from 3366566b237SFletcher Nichol`examples/data/route_guide_db.json` to `data/route_guide_db.json`. 337d9a481baSLucio Franco 338d9a481baSLucio FrancoNext, we need to implement `Hash` and `Eq` for `Point`, so we can use point values as map keys: 339d9a481baSLucio Franco 340d9a481baSLucio Franco```rust 341d9a481baSLucio Francouse std::hash::{Hasher, Hash}; 342d9a481baSLucio Franco``` 343d9a481baSLucio Franco 344d9a481baSLucio Franco```rust 345d9a481baSLucio Francoimpl Hash for Point { 346d9a481baSLucio Franco fn hash<H>(&self, state: &mut H) 347d9a481baSLucio Franco where 348d9a481baSLucio Franco H: Hasher, 349d9a481baSLucio Franco { 350d9a481baSLucio Franco self.latitude.hash(state); 351d9a481baSLucio Franco self.longitude.hash(state); 352d9a481baSLucio Franco } 353d9a481baSLucio Franco} 354d9a481baSLucio Franco 355d9a481baSLucio Francoimpl Eq for Point {} 356d9a481baSLucio Franco 357d9a481baSLucio Franco``` 358d9a481baSLucio Franco 359d9a481baSLucio FrancoLastly, we need implement two helper functions: `in_range` and `calc_distance`. We'll use them 360d9a481baSLucio Francowhen performing feature lookups. You can find them in 3616566b237SFletcher Nichol[examples/src/routeguide/server.rs][in-range-fn]. 362d9a481baSLucio Franco 3636566b237SFletcher Nichol[route-guide-db]: https://github.com/hyperium/tonic/blob/master/examples/data/route_guide_db.json 3646566b237SFletcher Nichol[data-module]: https://github.com/hyperium/tonic/blob/master/examples/src/routeguide/data.rs 365faa26ac1SJuan Alvarez[in-range-fn]: https://github.com/hyperium/tonic/blob/master/examples/src/routeguide/server.rs#L174 366d9a481baSLucio Franco 367d9a481baSLucio Franco#### Request and Response types 368d9a481baSLucio FrancoAll our service methods receive a `tonic::Request<T>` and return a 369d9a481baSLucio Franco`Result<tonic::Response<T>, tonic::Status>`. The concrete type of `T` depends on how our methods 370d9a481baSLucio Francoare declared in our *service* `.proto` definition. It can be either: 371d9a481baSLucio Franco 372d9a481baSLucio Franco- A single value, e.g `Point`, `Rectangle`, or even a message type that includes a repeated field. 373d9a481baSLucio Franco- A stream of values, e.g. `impl Stream<Item = Result<Feature, tonic::Status>>`. 374d9a481baSLucio Franco 375d9a481baSLucio Franco#### Simple RPC 376d9a481baSLucio FrancoLet's look at the simplest method first, `get_feature`, which just gets a `tonic::Request<Point>` 377d9a481baSLucio Francofrom the client and tries to find a feature at the given `Point`. If no feature is found, it returns 378d9a481baSLucio Francoan empty one. 379d9a481baSLucio Franco 380d9a481baSLucio Franco```rust 381d9a481baSLucio Francoasync fn get_feature(&self, request: Request<Point>) -> Result<Response<Feature>, Status> { 382d9a481baSLucio Franco for feature in &self.features[..] { 383d9a481baSLucio Franco if feature.location.as_ref() == Some(request.get_ref()) { 384d9a481baSLucio Franco return Ok(Response::new(feature.clone())); 385d9a481baSLucio Franco } 386d9a481baSLucio Franco } 387d9a481baSLucio Franco 3884e5c6c8fSJuan Alvarez Ok(Response::new(Feature::default())) 389d9a481baSLucio Franco} 390d9a481baSLucio Franco``` 391d9a481baSLucio Franco 392d9a481baSLucio Franco 393d9a481baSLucio Franco#### Server-side streaming RPC 394d9a481baSLucio FrancoNow let's look at one of our streaming RPCs. `list_features` is a server-side streaming RPC, so we 395d9a481baSLucio Franconeed to send back multiple `Feature`s to our client. 396d9a481baSLucio Franco 397d9a481baSLucio Franco```rust 398db22c356SJunichi Sugiuratype ListFeaturesStream = ReceiverStream<Result<Feature, Status>>; 399d9a481baSLucio Franco 400d9a481baSLucio Francoasync fn list_features( 401d9a481baSLucio Franco &self, 402d9a481baSLucio Franco request: Request<Rectangle>, 403d9a481baSLucio Franco) -> Result<Response<Self::ListFeaturesStream>, Status> { 4041442a59dSArjan Singh Bal let (tx, rx) = mpsc::channel(4); 405d9a481baSLucio Franco let features = self.features.clone(); 406d9a481baSLucio Franco 407d9a481baSLucio Franco tokio::spawn(async move { 408d9a481baSLucio Franco for feature in &features[..] { 409d9a481baSLucio Franco if in_range(feature.location.as_ref().unwrap(), request.get_ref()) { 410d9a481baSLucio Franco tx.send(Ok(feature.clone())).await.unwrap(); 411d9a481baSLucio Franco } 412d9a481baSLucio Franco } 413d9a481baSLucio Franco }); 414d9a481baSLucio Franco 415db22c356SJunichi Sugiura Ok(Response::new(ReceiverStream::new(rx))) 416d9a481baSLucio Franco} 417d9a481baSLucio Franco``` 418d9a481baSLucio Franco 419d9a481baSLucio FrancoLike `get_feature`, `list_features`'s input is a single message, a `Rectangle` in this 420d9a481baSLucio Francocase. This time, however, we need to return a stream of values, rather than a single one. 421d9a481baSLucio FrancoWe create a channel and spawn a new asynchronous task where we perform a lookup, sending 422d9a481baSLucio Francothe features that satisfy our constraints into the channel. 423d9a481baSLucio Franco 424d9a481baSLucio FrancoThe `Stream` half of the channel is returned to the caller, wrapped in a `tonic::Response`. 425d9a481baSLucio Franco 426d9a481baSLucio Franco 427d9a481baSLucio Franco#### Client-side streaming RPC 428d9a481baSLucio FrancoNow let's look at something a little more complicated: the client-side streaming method 429d9a481baSLucio Franco`record_route`, where we get a stream of `Point`s from the client and return a single `RouteSummary` 430d9a481baSLucio Francowith information about their trip. As you can see, this time the method receives a 431d9a481baSLucio Franco`tonic::Request<tonic::Streaming<Point>>`. 432d9a481baSLucio Franco 433d9a481baSLucio Franco```rust 434d9a481baSLucio Francouse std::time::Instant; 43519e5f396Stottotouse tokio_stream::StreamExt; 436d9a481baSLucio Franco``` 437d9a481baSLucio Franco 438d9a481baSLucio Franco```rust 439d9a481baSLucio Francoasync fn record_route( 440d9a481baSLucio Franco &self, 441d9a481baSLucio Franco request: Request<tonic::Streaming<Point>>, 442d9a481baSLucio Franco) -> Result<Response<RouteSummary>, Status> { 44334865a9fSDizans let mut stream = request.into_inner(); 444d9a481baSLucio Franco 445d9a481baSLucio Franco let mut summary = RouteSummary::default(); 446d9a481baSLucio Franco let mut last_point = None; 447d9a481baSLucio Franco let now = Instant::now(); 448d9a481baSLucio Franco 449d9a481baSLucio Franco while let Some(point) = stream.next().await { 450d9a481baSLucio Franco let point = point?; 451d9a481baSLucio Franco summary.point_count += 1; 452d9a481baSLucio Franco 453d9a481baSLucio Franco for feature in &self.features[..] { 454d9a481baSLucio Franco if feature.location.as_ref() == Some(&point) { 455d9a481baSLucio Franco summary.feature_count += 1; 456d9a481baSLucio Franco } 457d9a481baSLucio Franco } 458d9a481baSLucio Franco 459d9a481baSLucio Franco if let Some(ref last_point) = last_point { 460d9a481baSLucio Franco summary.distance += calc_distance(last_point, &point); 461d9a481baSLucio Franco } 462d9a481baSLucio Franco 463d9a481baSLucio Franco last_point = Some(point); 464d9a481baSLucio Franco } 465d9a481baSLucio Franco 466d9a481baSLucio Franco summary.elapsed_time = now.elapsed().as_secs() as i32; 467d9a481baSLucio Franco 468d9a481baSLucio Franco Ok(Response::new(summary)) 469d9a481baSLucio Franco} 470d9a481baSLucio Franco``` 471d9a481baSLucio Franco 472d9a481baSLucio Franco`record_route` is conceptually simple: we get a stream of `Points` and fold it into a `RouteSummary`. 473d9a481baSLucio FrancoIn other words, we build a summary value as we process each `Point` in our stream, one by one. 474d9a481baSLucio FrancoWhen there are no more `Points` in our stream, we return the `RouteSummary` wrapped in a 475d9a481baSLucio Franco`tonic::Response`. 476d9a481baSLucio Franco 477d9a481baSLucio Franco#### Bidirectional streaming RPC 478d9a481baSLucio FrancoFinally, let's look at our bidirectional streaming RPC `route_chat`, which receives a stream 479d9a481baSLucio Francoof `RouteNote`s and returns a stream of `RouteNote`s. 480d9a481baSLucio Franco 481d9a481baSLucio Franco```rust 482faa26ac1SJuan Alvarezuse std::collections::HashMap; 483faa26ac1SJuan Alvarez``` 484faa26ac1SJuan Alvarez 485faa26ac1SJuan Alvarez```rust 486faa26ac1SJuan Alvareztype RouteChatStream = 48723c1392fSLucio Franco Pin<Box<dyn Stream<Item = Result<RouteNote, Status>> + Send + 'static>>; 488faa26ac1SJuan Alvarez 489d9a481baSLucio Franco 490d9a481baSLucio Francoasync fn route_chat( 491d9a481baSLucio Franco &self, 492d9a481baSLucio Franco request: Request<tonic::Streaming<RouteNote>>, 493d9a481baSLucio Franco) -> Result<Response<Self::RouteChatStream>, Status> { 494d9a481baSLucio Franco let mut notes = HashMap::new(); 49534865a9fSDizans let mut stream = request.into_inner(); 496d9a481baSLucio Franco 497d9a481baSLucio Franco let output = async_stream::try_stream! { 498d9a481baSLucio Franco while let Some(note) = stream.next().await { 499d9a481baSLucio Franco let note = note?; 500d9a481baSLucio Franco 501b2e56654Stottoto let location = note.location.unwrap(); 502d9a481baSLucio Franco 503d9a481baSLucio Franco let location_notes = notes.entry(location).or_insert(vec![]); 504d9a481baSLucio Franco location_notes.push(note); 505d9a481baSLucio Franco 506d9a481baSLucio Franco for note in location_notes { 507d9a481baSLucio Franco yield note.clone(); 508d9a481baSLucio Franco } 509d9a481baSLucio Franco } 510d9a481baSLucio Franco }; 511d9a481baSLucio Franco 512d9a481baSLucio Franco Ok(Response::new(Box::pin(output) 5139c954e09STunahan Karlıbaş as Self::RouteChatStream)) 514d9a481baSLucio Franco 515d9a481baSLucio Franco} 516d9a481baSLucio Franco``` 517d9a481baSLucio Franco 518d9a481baSLucio Franco`route_chat` uses the [async-stream] crate to perform an asynchronous transformation 519d9a481baSLucio Francofrom one (input) stream to another (output) stream. As the input is processed, each value is 520d9a481baSLucio Francoinserted into the notes map, yielding a clone of the original `RouteNote`. The resulting stream 521d9a481baSLucio Francois then returned to the caller. Neat. 522d9a481baSLucio Franco 523d9a481baSLucio Franco**Note**: The funky `as` cast is needed due to a limitation in the rust compiler. This is expected 524d9a481baSLucio Francoto be fixed soon. 525d9a481baSLucio Franco 526d9a481baSLucio Franco[async-stream]: https://github.com/tokio-rs/async-stream 527d9a481baSLucio Franco 528d9a481baSLucio Franco### Starting the server 529d9a481baSLucio Franco 530d9a481baSLucio FrancoOnce we've implemented all our methods, we also need to start up a gRPC server so that clients can 531d9a481baSLucio Francoactually use our service. This is how our `main` function looks like: 532d9a481baSLucio Franco 533d9a481baSLucio Franco```rust 534d9a481baSLucio Francomod data; 535d9a481baSLucio Francouse tonic::transport::Server; 536d9a481baSLucio Franco``` 537d9a481baSLucio Franco 538d9a481baSLucio Franco```rust 539d9a481baSLucio Franco#[tokio::main] 540d9a481baSLucio Francoasync fn main() -> Result<(), Box<dyn std::error::Error>> { 541d9a481baSLucio Franco let addr = "[::1]:10000".parse().unwrap(); 542d9a481baSLucio Franco 543faa26ac1SJuan Alvarez let route_guide = RouteGuideService { 544d9a481baSLucio Franco features: Arc::new(data::load()), 545d9a481baSLucio Franco }; 546d9a481baSLucio Franco 5474e5c6c8fSJuan Alvarez let svc = RouteGuideServer::new(route_guide); 548d9a481baSLucio Franco 5494e5c6c8fSJuan Alvarez Server::builder().add_service(svc).serve(addr).await?; 550d9a481baSLucio Franco 551d9a481baSLucio Franco Ok(()) 552d9a481baSLucio Franco} 553d9a481baSLucio Franco``` 554d9a481baSLucio Franco 555d9a481baSLucio FrancoTo handle requests, `Tonic` uses [Tower] and [hyper] internally. What this means, 556d9a481baSLucio Francoamong other things, is that we have a flexible and composable stack we can build on top of. We can, 557faa26ac1SJuan Alvarezfor example, add an [interceptor][authentication-example] to process requests before they reach our service 558faa26ac1SJuan Alvarezmethods. 559d9a481baSLucio Franco 560d9a481baSLucio Franco 561d9a481baSLucio Franco[Tower]: https://github.com/tower-rs 562d9a481baSLucio Franco[hyper]: https://github.com/hyperium/hyper 5636566b237SFletcher Nichol[authentication-example]: https://github.com/hyperium/tonic/blob/master/examples/src/authentication/server.rs#L56 564d9a481baSLucio Franco 565d9a481baSLucio Franco<a name="client"></a> 566d9a481baSLucio Franco## Creating the client 567d9a481baSLucio Franco 568d9a481baSLucio FrancoIn this section, we'll look at creating a Tonic client for our `RouteGuide` service. You can see our 5696566b237SFletcher Nicholcomplete example client code in [examples/src/routeguide/client.rs][routeguide-client]. 570d9a481baSLucio Franco 571d9a481baSLucio FrancoOur crate will have two binary targets: `routeguide-client` and `routeguide-server`. We need to 572d9a481baSLucio Francoedit our `Cargo.toml` accordingly: 573d9a481baSLucio Franco 574d9a481baSLucio Franco```toml 575d9a481baSLucio Franco[[bin]] 576d9a481baSLucio Franconame = "routeguide-server" 577d9a481baSLucio Francopath = "src/server.rs" 578d9a481baSLucio Franco 579d9a481baSLucio Franco[[bin]] 580d9a481baSLucio Franconame = "routeguide-client" 581d9a481baSLucio Francopath = "src/client.rs" 582d9a481baSLucio Franco``` 583d9a481baSLucio Franco 584d9a481baSLucio FrancoRename `main.rs` to `server.rs` and create a new file `client.rs`. 585d9a481baSLucio Franco 586d9a481baSLucio Franco```shell 587d9a481baSLucio Franco$ mv src/main.rs src/server.rs 588d9a481baSLucio Franco$ touch src/client.rs 589d9a481baSLucio Franco``` 590d9a481baSLucio Franco 591faa26ac1SJuan AlvarezTo call service methods, we first need to create a gRPC *client* to communicate with the server. Like in the server 592faa26ac1SJuan Alvarezcase, we'll start by bringing the generated code into scope: 593d9a481baSLucio Franco 594d9a481baSLucio Franco```rust 5954e5c6c8fSJuan Alvarezpub mod routeguide { 596d9a481baSLucio Franco tonic::include_proto!("routeguide"); 597d9a481baSLucio Franco} 598d9a481baSLucio Franco 5994e5c6c8fSJuan Alvarezuse routeguide::route_guide_client::RouteGuideClient; 6004e5c6c8fSJuan Alvarezuse routeguide::{Point, Rectangle, RouteNote}; 6014e5c6c8fSJuan Alvarez 602d9a481baSLucio Franco 603d9a481baSLucio Franco#[tokio::main] 604d9a481baSLucio Francoasync fn main() -> Result<(), Box<dyn std::error::Error>> { 605d9a481baSLucio Franco let mut client = RouteGuideClient::connect("http://[::1]:10000").await?; 606d9a481baSLucio Franco 607d9a481baSLucio Franco Ok(()) 608d9a481baSLucio Franco} 609d9a481baSLucio Franco``` 610d9a481baSLucio Franco 611d9a481baSLucio FrancoSame as in the server implementation, we start by bringing our generated code into scope. We then 612d9a481baSLucio Francocreate a client in our main function, passing the server's full URL to `RouteGuideClient::connect`. 613d9a481baSLucio FrancoOur client is now ready to make service calls. Note that `client` is mutable, this is because it 614d9a481baSLucio Franconeeds to manage internal state. 615d9a481baSLucio Franco 6166566b237SFletcher Nichol[routeguide-client]: https://github.com/hyperium/tonic/blob/master/examples/src/routeguide/client.rs 617d9a481baSLucio Franco 618d9a481baSLucio Franco 619d9a481baSLucio Franco### Calling service methods 620d9a481baSLucio FrancoNow let's look at how we call our service methods. Note that in Tonic, RPCs are asynchronous, 621d9a481baSLucio Francowhich means that RPC calls need to be `.await`ed. 622d9a481baSLucio Franco 623d9a481baSLucio Franco#### Simple RPC 624d9a481baSLucio FrancoCalling the simple RPC `get_feature` is as straightforward as calling a local method: 625d9a481baSLucio Franco 626d9a481baSLucio Franco```rust 627d9a481baSLucio Francouse tonic::Request; 628d9a481baSLucio Franco``` 629d9a481baSLucio Franco 630d9a481baSLucio Franco```rust 631d9a481baSLucio Francolet response = client 632d9a481baSLucio Franco .get_feature(Request::new(Point { 633d9a481baSLucio Franco latitude: 409146138, 634d9a481baSLucio Franco longitude: -746188906, 635d9a481baSLucio Franco })) 636d9a481baSLucio Franco .await?; 637d9a481baSLucio Franco 638d9a481baSLucio Francoprintln!("RESPONSE = {:?}", response); 639d9a481baSLucio Franco``` 640d9a481baSLucio FrancoWe call the `get_feature` client method, passing a single `Point` value wrapped in a 641d9a481baSLucio Franco`tonic::Request`. We get a `Result<tonic::Response<Feature>, tonic::Status>` back. 642d9a481baSLucio Franco 643d9a481baSLucio Franco#### Server-side streaming RPC 644d9a481baSLucio FrancoHere's where we call the server-side streaming method `list_features`, which returns a stream of 645d9a481baSLucio Francogeographical `Feature`s. 646d9a481baSLucio Franco 647d9a481baSLucio Franco```rust 648d9a481baSLucio Francouse tonic::transport::Channel; 649d9a481baSLucio Francouse std::error::Error; 650d9a481baSLucio Franco``` 651d9a481baSLucio Franco 652d9a481baSLucio Franco```rust 653d9a481baSLucio Francoasync fn print_features(client: &mut RouteGuideClient<Channel>) -> Result<(), Box<dyn Error>> { 654d9a481baSLucio Franco let rectangle = Rectangle { 655d9a481baSLucio Franco lo: Some(Point { 656d9a481baSLucio Franco latitude: 400000000, 657d9a481baSLucio Franco longitude: -750000000, 658d9a481baSLucio Franco }), 659d9a481baSLucio Franco hi: Some(Point { 660d9a481baSLucio Franco latitude: 420000000, 661d9a481baSLucio Franco longitude: -730000000, 662d9a481baSLucio Franco }), 663d9a481baSLucio Franco }; 664d9a481baSLucio Franco 665d9a481baSLucio Franco let mut stream = client 666d9a481baSLucio Franco .list_features(Request::new(rectangle)) 667d9a481baSLucio Franco .await? 668d9a481baSLucio Franco .into_inner(); 669d9a481baSLucio Franco 670d9a481baSLucio Franco while let Some(feature) = stream.message().await? { 6711442a59dSArjan Singh Bal println!("FEATURE = {:?}", feature); 672d9a481baSLucio Franco } 673d9a481baSLucio Franco 674d9a481baSLucio Franco Ok(()) 675d9a481baSLucio Franco} 676d9a481baSLucio Franco``` 677d9a481baSLucio Franco 678d9a481baSLucio FrancoAs in the simple RPC, we pass a single value request. However, instead of getting a 679d9a481baSLucio Francosingle value back, we get a stream of `Features`. 680d9a481baSLucio Franco 681eeb3268fSpavedroadWe use the `message()` method from the `tonic::Streaming` struct to repeatedly read in the 682d9a481baSLucio Francoserver's responses to a response protocol buffer object (in this case a `Feature`) until there are 683d9a481baSLucio Francono more messages left in the stream. 684d9a481baSLucio Franco 685d9a481baSLucio Franco#### Client-side streaming RPC 686d9a481baSLucio FrancoThe client-side streaming method `record_route` takes a stream of `Point`s and returns a single 687d9a481baSLucio Franco`RouteSummary` value. 688d9a481baSLucio Franco 689d9a481baSLucio Franco```rust 690d9a481baSLucio Francouse rand::rngs::ThreadRng; 691d9a481baSLucio Francouse rand::Rng; 692d9a481baSLucio Franco``` 693d9a481baSLucio Franco 694d9a481baSLucio Franco```rust 695d9a481baSLucio Francoasync fn run_record_route(client: &mut RouteGuideClient<Channel>) -> Result<(), Box<dyn Error>> { 696*f07abb27Stottoto let mut rng = rand::rng(); 697*f07abb27Stottoto let point_count: i32 = rng.random_range(2..100); 698d9a481baSLucio Franco 699d9a481baSLucio Franco let mut points = vec![]; 700d9a481baSLucio Franco for _ in 0..=point_count { 701d9a481baSLucio Franco points.push(random_point(&mut rng)) 702d9a481baSLucio Franco } 703d9a481baSLucio Franco 704d9a481baSLucio Franco println!("Traversing {} points", points.len()); 70519e5f396Stottoto let request = Request::new(tokio_stream::iter(points)); 706d9a481baSLucio Franco 707d9a481baSLucio Franco match client.record_route(request).await { 708d9a481baSLucio Franco Ok(response) => println!("SUMMARY: {:?}", response.into_inner()), 709d9a481baSLucio Franco Err(e) => println!("something went wrong: {:?}", e), 710d9a481baSLucio Franco } 711d9a481baSLucio Franco 712d9a481baSLucio Franco Ok(()) 713d9a481baSLucio Franco} 714d9a481baSLucio Franco``` 715d9a481baSLucio Franco 716d9a481baSLucio Franco```rust 717d9a481baSLucio Francofn random_point(rng: &mut ThreadRng) -> Point { 718*f07abb27Stottoto let latitude = (rng.random_range(0..180) - 90) * 10_000_000; 719*f07abb27Stottoto let longitude = (rng.random_range(0..360) - 180) * 10_000_000; 720d9a481baSLucio Franco Point { 721d9a481baSLucio Franco latitude, 722d9a481baSLucio Franco longitude, 723d9a481baSLucio Franco } 724d9a481baSLucio Franco} 725d9a481baSLucio Franco``` 726d9a481baSLucio Franco 727d9a481baSLucio FrancoWe build a vector of a random number of `Point` values (between 2 and 100) and then convert 72819e5f396Stottotoit into a `Stream` using the `tokio_stream::iter` function. This is a cheap an easy way to get 729d9a481baSLucio Francoa stream suitable for passing into our service method. The resulting stream is then wrapped in a 730d9a481baSLucio Franco`tonic::Request`. 731d9a481baSLucio Franco 732d9a481baSLucio Franco 733d9a481baSLucio Franco#### Bidirectional streaming RPC 734d9a481baSLucio Franco 735d9a481baSLucio FrancoFinally, let's look at our bidirectional streaming RPC. The `route_chat` method takes a stream 736d9a481baSLucio Francoof `RouteNotes` and returns either another stream of `RouteNotes` or an error. 737d9a481baSLucio Franco 738d9a481baSLucio Franco```rust 7394e5c6c8fSJuan Alvarezuse std::time::Duration; 7404e5c6c8fSJuan Alvarezuse tokio::time; 741d9a481baSLucio Franco``` 742d9a481baSLucio Franco 743d9a481baSLucio Franco```rust 744d9a481baSLucio Francoasync fn run_route_chat(client: &mut RouteGuideClient<Channel>) -> Result<(), Box<dyn Error>> { 7454e5c6c8fSJuan Alvarez let start = time::Instant::now(); 746d9a481baSLucio Franco 747d9a481baSLucio Franco let outbound = async_stream::stream! { 7484e5c6c8fSJuan Alvarez let mut interval = time::interval(Duration::from_secs(1)); 749d9a481baSLucio Franco 750faa26ac1SJuan Alvarez while let time = interval.tick().await { 751d9a481baSLucio Franco let elapsed = time.duration_since(start); 752d9a481baSLucio Franco let note = RouteNote { 753d9a481baSLucio Franco location: Some(Point { 754d9a481baSLucio Franco latitude: 409146138 + elapsed.as_secs() as i32, 755d9a481baSLucio Franco longitude: -746188906, 756d9a481baSLucio Franco }), 757d9a481baSLucio Franco message: format!("at {:?}", elapsed), 758d9a481baSLucio Franco }; 759d9a481baSLucio Franco 760d9a481baSLucio Franco yield note; 761d9a481baSLucio Franco } 762d9a481baSLucio Franco }; 763d9a481baSLucio Franco 7644e5c6c8fSJuan Alvarez let response = client.route_chat(Request::new(outbound)).await?; 765d9a481baSLucio Franco let mut inbound = response.into_inner(); 766d9a481baSLucio Franco 767d9a481baSLucio Franco while let Some(note) = inbound.message().await? { 768d9a481baSLucio Franco println!("NOTE = {:?}", note); 769d9a481baSLucio Franco } 770d9a481baSLucio Franco 771d9a481baSLucio Franco Ok(()) 772d9a481baSLucio Franco} 773d9a481baSLucio Franco``` 774d9a481baSLucio FrancoIn this case, we use the [async-stream] crate to generate our outbound stream, yielding 775d9a481baSLucio Franco`RouteNote` values in one second intervals. We then iterate over the stream returned by 776d9a481baSLucio Francothe server, printing each value in the stream. 777d9a481baSLucio Franco 778d9a481baSLucio Franco## Try it out! 779d9a481baSLucio Franco 780d9a481baSLucio Franco### Run the server 781d9a481baSLucio Franco```shell 782d9a481baSLucio Franco$ cargo run --bin routeguide-server 783d9a481baSLucio Franco``` 784d9a481baSLucio Franco 785d9a481baSLucio Franco### Run the client 786d9a481baSLucio Franco```shell 787d9a481baSLucio Franco$ cargo run --bin routeguide-client 788d9a481baSLucio Franco``` 789d9a481baSLucio Franco 790d9a481baSLucio Franco## Appendix 791d9a481baSLucio Franco 792d9a481baSLucio Franco<a name="tonic-build"></a> 793d9a481baSLucio Franco### tonic_build configuration 794d9a481baSLucio Franco 795d9a481baSLucio FrancoTonic's default code generation configuration is convenient for self contained examples and small 796d9a481baSLucio Francoprojects. However, there are some cases when we need a slightly different workflow. For example: 797d9a481baSLucio Franco 798d9a481baSLucio Franco- When building rust clients and servers in different crates. 799d9a481baSLucio Franco- When building a rust client or server (or both) as part of a larger, multi-language project. 800d9a481baSLucio Franco- When we want editor support for the generate code and our editor does not index the generated 801d9a481baSLucio Francofiles in the default location. 802d9a481baSLucio Franco 803d9a481baSLucio FrancoMore generally, whenever we want to keep our `.proto` definitions in a central place and generate 804d9a481baSLucio Francocode for different crates or different languages, the default configuration is not enough. 805d9a481baSLucio Franco 806d9a481baSLucio FrancoLuckily, `tonic_build` can be configured to fit whatever workflow we need. Here are just two 807d9a481baSLucio Francopossibilities: 808d9a481baSLucio Franco 809d9a481baSLucio Franco1) We can keep our `.proto` definitions in a separate crate and generate our code on demand, as 810d9a481baSLucio Francoopposed to at build time, placing the resulting modules wherever we need them. 811d9a481baSLucio Franco 812d9a481baSLucio Franco`main.rs` 813d9a481baSLucio Franco 814d9a481baSLucio Franco```rust 815d9a481baSLucio Francofn main() { 8162e082f8bSLucio Franco tonic_build::configure() 817d9a481baSLucio Franco .build_client(false) 818d9a481baSLucio Franco .out_dir("another_crate/src/pb") 8195965daa8Stottoto .compile_protos(&["path/my_proto.proto"], &["path"]) 820d9a481baSLucio Franco .expect("failed to compile protos"); 821d9a481baSLucio Franco} 822d9a481baSLucio Franco``` 823d9a481baSLucio Franco 824f82488c4S虎On `cargo run`, this will generate code for the server only, and place the resulting file in 825d9a481baSLucio Franco`another_crate/src/pb`. 826d9a481baSLucio Franco 827d9a481baSLucio Franco2) Similarly, we could also keep the `.proto` definitions in a separate crate and then use that 828d9a481baSLucio Francocrate as a direct dependency wherever we need it. 829