1d9a481baSLucio Franco# Getting Started 2d9a481baSLucio Franco 3d9a481baSLucio FrancoThis tutorial is meant to be an introduction to Tonic and assumes that you have basic [Rust] experience as well as an understanding of what [protocol buffers] are. If you don't, feel free to read up on the pages linked in this paragraph and come back to this tutorial once you feel you are ready! 4d9a481baSLucio Franco 5d9a481baSLucio Franco[rust]: https://www.rust-lang.org/ 6d9a481baSLucio Franco[protocol buffers]: https://developers.google.com/protocol-buffers/docs/overview 7d9a481baSLucio Franco 8d9a481baSLucio Franco## Prerequisites 9d9a481baSLucio Franco 10d9a481baSLucio FrancoTo run the sample code and walk through the tutorial, the only prerequisite is Rust itself. 11d9a481baSLucio Franco[rustup] is a convenient tool to install it, if you haven't already. 12d9a481baSLucio Franco 13d9a481baSLucio Franco[rustup]: https://rustup.rs 14d9a481baSLucio Franco 15d9a481baSLucio Franco## Project Setup 16d9a481baSLucio Franco 17d9a481baSLucio FrancoFor this tutorial, we will start by creating a new Rust project with Cargo: 18d9a481baSLucio Franco 19d9a481baSLucio Franco```shell 20d9a481baSLucio Franco$ cargo new helloworld-tonic 21d9a481baSLucio Franco$ cd helloworld-tonic 22d9a481baSLucio Franco``` 23d9a481baSLucio Franco 24d9a481baSLucio Franco`tonic` works on rust `1.39` and above as it requires support for the `async_await` 25d9a481baSLucio Francofeature. 26d9a481baSLucio Franco 27d9a481baSLucio Franco```bash 28d9a481baSLucio Franco$ rustup update 29d9a481baSLucio Franco``` 30d9a481baSLucio Franco 31d9a481baSLucio Franco## Defining the HelloWorld service 32d9a481baSLucio Franco 33d9a481baSLucio FrancoOur first step is to define the gRPC _service_ and the method _request_ and _response_ types using 34c62f382eSMfon Eti-mfon[protocol buffers]. We will keep our `.proto` files in a directory in our project's root. 35d9a481baSLucio FrancoNote that Tonic does not really care where our `.proto` definitions live. 36d9a481baSLucio Franco 37d9a481baSLucio Franco```shell 38d9a481baSLucio Franco$ mkdir proto 39d9a481baSLucio Franco$ touch proto/helloworld.proto 40d9a481baSLucio Franco``` 41d9a481baSLucio Franco 42d9a481baSLucio FrancoThen you define RPC methods inside your service definition, specifying their request and response 43d9a481baSLucio Francotypes. gRPC lets you define four kinds of service methods, all of which are supported by Tonic. For this tutorial we will only use a simple RPC, if you would like to see a Tonic example which uses all four kinds please read the [routeguide tutorial]. 44d9a481baSLucio Franco 457077d8dfSArtem Vorotnikov[routeguide tutorial]: https://github.com/hyperium/tonic/blob/master/examples/routeguide-tutorial.md 46d9a481baSLucio Franco 47d9a481baSLucio FrancoFirst we define our package name, which is what Tonic looks for when including your protos in the client and server applications. Lets give this one a name of `helloworld`. 48d9a481baSLucio Franco 49d9a481baSLucio Franco```proto 50d9a481baSLucio Francosyntax = "proto3"; 51d9a481baSLucio Francopackage helloworld; 52d9a481baSLucio Franco``` 53d9a481baSLucio Franco 54d9a481baSLucio FrancoNext we need to define our service. This service will contain the actual RPC calls we will be using in our application. An RPC contains an Identifier, a Request type, and returns a Response type. Here is our Greeter service, which provides the SayHello RPC method. 55d9a481baSLucio Franco 56d9a481baSLucio Franco```proto 57d9a481baSLucio Francoservice Greeter { 58d9a481baSLucio Franco // Our SayHello rpc accepts HelloRequests and returns HelloReplies 59d9a481baSLucio Franco rpc SayHello (HelloRequest) returns (HelloReply); 60d9a481baSLucio Franco} 61d9a481baSLucio Franco``` 62d9a481baSLucio Franco 63d9a481baSLucio FrancoFinally, we have to actually define those types we used above in our `SayHello` RPC method. RPC types are defined as messages which contain typed fields. Here is what that will look like for our HelloWorld application: 64d9a481baSLucio Franco 65d9a481baSLucio Franco```proto 66d9a481baSLucio Francomessage HelloRequest { 67d9a481baSLucio Franco // Request message contains the name to be greeted 68d9a481baSLucio Franco string name = 1; 69d9a481baSLucio Franco} 70d9a481baSLucio Franco 71d9a481baSLucio Francomessage HelloReply { 72d9a481baSLucio Franco // Reply contains the greeting message 73d9a481baSLucio Franco string message = 1; 74d9a481baSLucio Franco} 75d9a481baSLucio Franco``` 76d9a481baSLucio Franco 77d9a481baSLucio FrancoGreat! Now our `.proto` file should be complete and ready for use in our application. Here is what it should look like completed: 78d9a481baSLucio Franco 79d9a481baSLucio Franco```proto 80d9a481baSLucio Francosyntax = "proto3"; 81d9a481baSLucio Francopackage helloworld; 82d9a481baSLucio Franco 83d9a481baSLucio Francoservice Greeter { 84d9a481baSLucio Franco rpc SayHello (HelloRequest) returns (HelloReply); 85d9a481baSLucio Franco} 86d9a481baSLucio Franco 87d9a481baSLucio Francomessage HelloRequest { 88d9a481baSLucio Franco string name = 1; 89d9a481baSLucio Franco} 90d9a481baSLucio Franco 91d9a481baSLucio Francomessage HelloReply { 92d9a481baSLucio Franco string message = 1; 93d9a481baSLucio Franco} 94d9a481baSLucio Franco``` 95d9a481baSLucio Franco 96d9a481baSLucio Franco## Application Setup 97d9a481baSLucio Franco 98d9a481baSLucio FrancoNow that have defined the protobuf for our application we can start writing our application with Tonic! Let's first add our required dependencies to the `Cargo.toml`. 99d9a481baSLucio Franco 100d9a481baSLucio Franco```toml 101d9a481baSLucio Franco[package] 102d9a481baSLucio Franconame = "helloworld-tonic" 103d9a481baSLucio Francoversion = "0.1.0" 1041f3df8dbSLucio Francoedition = "2021" 105d9a481baSLucio Franco 106d9a481baSLucio Franco[[bin]] # Bin to run the HelloWorld gRPC server 107d9a481baSLucio Franconame = "helloworld-server" 108d9a481baSLucio Francopath = "src/server.rs" 109d9a481baSLucio Franco 110d9a481baSLucio Franco[[bin]] # Bin to run the HelloWorld gRPC client 111d9a481baSLucio Franconame = "helloworld-client" 112d9a481baSLucio Francopath = "src/client.rs" 113d9a481baSLucio Franco 114d9a481baSLucio Franco[dependencies] 115*eb66b914SEaswar Swaminathantonic = "*" 1164aad5afaSLukeprost = "0.13" 11707d2f982S16yuki0702tokio = { version = "1.0", features = ["macros", "rt-multi-thread"] } 118d9a481baSLucio Franco 119d9a481baSLucio Franco[build-dependencies] 120*eb66b914SEaswar Swaminathantonic-build = "*" 121d9a481baSLucio Franco``` 122d9a481baSLucio Franco 123d9a481baSLucio FrancoWe include `tonic-build` as a useful way to incorporate the generation of our client and server gRPC code into the build process of our application. We will setup this build process now: 124d9a481baSLucio Franco 125d9a481baSLucio Franco## Generating Server and Client code 126d9a481baSLucio Franco 127ec9ee619SJoseph LeBlancAt the root of your project (not /src), create a `build.rs` file and add the following code: 128d9a481baSLucio Franco 129d9a481baSLucio Franco```rust 130d9a481baSLucio Francofn main() -> Result<(), Box<dyn std::error::Error>> { 1314f839107SNick Law tonic_build::compile_protos("proto/helloworld.proto")?; 132d9a481baSLucio Franco Ok(()) 133d9a481baSLucio Franco} 134d9a481baSLucio Franco``` 135d9a481baSLucio Franco 136d9a481baSLucio FrancoThis tells `tonic-build` to compile your protobufs when you build your Rust project. While you can configure this build process in a number of ways, we will not get into the details in this introductory tutorial. Please see the [tonic-build] documentation for details on configuration. 137d9a481baSLucio Franco 138d9a481baSLucio Franco[tonic-build]: https://github.com/hyperium/tonic/blob/master/tonic-build/README.md 139d9a481baSLucio Franco 140d9a481baSLucio Franco## Writing our Server 141d9a481baSLucio Franco 142d9a481baSLucio FrancoNow that the build process is written and our dependencies are all setup, we can begin writing the fun stuff! We need to import the things we will be using in our server, including the protobuf. Start by making a file called `server.rs` in your `/src` directory and writing the following code: 143d9a481baSLucio Franco 144d9a481baSLucio Franco```rust 145d9a481baSLucio Francouse tonic::{transport::Server, Request, Response, Status}; 146d9a481baSLucio Franco 1474e5c6c8fSJuan Alvarezuse hello_world::greeter_server::{Greeter, GreeterServer}; 1484e5c6c8fSJuan Alvarezuse hello_world::{HelloReply, HelloRequest}; 1494e5c6c8fSJuan Alvarez 150d9a481baSLucio Francopub mod hello_world { 151d9a481baSLucio Franco tonic::include_proto!("helloworld"); // The string specified here must match the proto package name 152d9a481baSLucio Franco} 153d9a481baSLucio Franco``` 154d9a481baSLucio Franco 155d9a481baSLucio FrancoNext up, let's implement the Greeter service we previously defined in our `.proto` file. Here's what that might look like: 156d9a481baSLucio Franco 157d9a481baSLucio Franco```rust 1584e5c6c8fSJuan Alvarez#[derive(Debug, Default)] 159d9a481baSLucio Francopub struct MyGreeter {} 160d9a481baSLucio Franco 161d9a481baSLucio Franco#[tonic::async_trait] 162d9a481baSLucio Francoimpl Greeter for MyGreeter { 163d9a481baSLucio Franco async fn say_hello( 164d9a481baSLucio Franco &self, 165d9a481baSLucio Franco request: Request<HelloRequest>, // Accept request of type HelloRequest 166d9a481baSLucio Franco ) -> Result<Response<HelloReply>, Status> { // Return an instance of type HelloReply 167d9a481baSLucio Franco println!("Got a request: {:?}", request); 168d9a481baSLucio Franco 169d9fa6739SMaxime let reply = HelloReply { 17042274686SNetanel Rabinowitz message: format!("Hello {}!", request.into_inner().name), // We must use .into_inner() as the fields of gRPC requests and responses are private 171d9a481baSLucio Franco }; 172d9a481baSLucio Franco 173d9a481baSLucio Franco Ok(Response::new(reply)) // Send back our formatted greeting 174d9a481baSLucio Franco } 175d9a481baSLucio Franco} 176d9a481baSLucio Franco``` 177d9a481baSLucio Franco 178d9a481baSLucio FrancoFinally, let's define the Tokio runtime that our server will actually run on. This requires Tokio to be added as a dependency, so make sure you included that! 179d9a481baSLucio Franco 180d9a481baSLucio Franco```rust 181d9a481baSLucio Franco#[tokio::main] 182d9a481baSLucio Francoasync fn main() -> Result<(), Box<dyn std::error::Error>> { 183d9a481baSLucio Franco let addr = "[::1]:50051".parse()?; 1844e5c6c8fSJuan Alvarez let greeter = MyGreeter::default(); 185d9a481baSLucio Franco 186d9a481baSLucio Franco Server::builder() 187d9a481baSLucio Franco .add_service(GreeterServer::new(greeter)) 188d9a481baSLucio Franco .serve(addr) 189d9a481baSLucio Franco .await?; 190d9a481baSLucio Franco 191d9a481baSLucio Franco Ok(()) 192d9a481baSLucio Franco} 193d9a481baSLucio Franco``` 194d9a481baSLucio Franco 195d9a481baSLucio FrancoAltogether your server should look something like this once you are done: 196d9a481baSLucio Franco 197d9a481baSLucio Franco```rust 198d9a481baSLucio Francouse tonic::{transport::Server, Request, Response, Status}; 199d9a481baSLucio Franco 2004e5c6c8fSJuan Alvarezuse hello_world::greeter_server::{Greeter, GreeterServer}; 2014e5c6c8fSJuan Alvarezuse hello_world::{HelloReply, HelloRequest}; 2024e5c6c8fSJuan Alvarez 203d9a481baSLucio Francopub mod hello_world { 204d9a481baSLucio Franco tonic::include_proto!("helloworld"); 205d9a481baSLucio Franco} 206d9a481baSLucio Franco 2074e5c6c8fSJuan Alvarez#[derive(Debug, Default)] 208d9a481baSLucio Francopub struct MyGreeter {} 209d9a481baSLucio Franco 210d9a481baSLucio Franco#[tonic::async_trait] 211d9a481baSLucio Francoimpl Greeter for MyGreeter { 212d9a481baSLucio Franco async fn say_hello( 213d9a481baSLucio Franco &self, 214d9a481baSLucio Franco request: Request<HelloRequest>, 215d9a481baSLucio Franco ) -> Result<Response<HelloReply>, Status> { 216d9a481baSLucio Franco println!("Got a request: {:?}", request); 217d9a481baSLucio Franco 218d9fa6739SMaxime let reply = HelloReply { 21942274686SNetanel Rabinowitz message: format!("Hello {}!", request.into_inner().name), 220d9a481baSLucio Franco }; 221d9a481baSLucio Franco 222d9a481baSLucio Franco Ok(Response::new(reply)) 223d9a481baSLucio Franco } 224d9a481baSLucio Franco} 225d9a481baSLucio Franco 226d9a481baSLucio Franco#[tokio::main] 227d9a481baSLucio Francoasync fn main() -> Result<(), Box<dyn std::error::Error>> { 228d9a481baSLucio Franco let addr = "[::1]:50051".parse()?; 2294e5c6c8fSJuan Alvarez let greeter = MyGreeter::default(); 230d9a481baSLucio Franco 231d9a481baSLucio Franco Server::builder() 232d9a481baSLucio Franco .add_service(GreeterServer::new(greeter)) 233d9a481baSLucio Franco .serve(addr) 234d9a481baSLucio Franco .await?; 235d9a481baSLucio Franco 236d9a481baSLucio Franco Ok(()) 237d9a481baSLucio Franco} 238d9a481baSLucio Franco``` 239d9a481baSLucio Franco 240bd0f5eddSFuyang LiuYou should now be able to run your HelloWorld gRPC server using the command `cargo run --bin helloworld-server`. This uses the [[bin]] we defined earlier in our `Cargo.toml` to run specifically the server. 241bd0f5eddSFuyang Liu 242f613386dSJaydenElliottIf you have a gRPC GUI client such as [Bloom RPC] you should be able to send requests to the server and get back greetings! 243bd0f5eddSFuyang Liu 244bd0f5eddSFuyang LiuOr if you use [grpcurl] then you can simply try send requests like this: 245bd0f5eddSFuyang Liu``` 246dcb00262SSheng Zheng$ grpcurl -plaintext -import-path ./proto -proto helloworld.proto -d '{"name": "Tonic"}' '[::1]:50051' helloworld.Greeter/SayHello 247bd0f5eddSFuyang Liu``` 248bd0f5eddSFuyang LiuAnd receiving responses like this: 249bd0f5eddSFuyang Liu``` 250bd0f5eddSFuyang Liu{ 251bd0f5eddSFuyang Liu "message": "Hello Tonic!" 252bd0f5eddSFuyang Liu} 253bd0f5eddSFuyang Liu``` 254d9a481baSLucio Franco 255d9a481baSLucio Franco[bloom rpc]: https://github.com/uw-labs/bloomrpc 256bd0f5eddSFuyang Liu[grpcurl]: https://github.com/fullstorydev/grpcurl 257d9a481baSLucio Franco 258d9a481baSLucio Franco## Writing our Client 259d9a481baSLucio Franco 260d9a481baSLucio FrancoSo now we have a running gRPC server, and that's great but how can our application communicate with it? This is where our client would come in. Tonic supports both client and server implementations. Similar to the server, we will start by creating a file `client.rs` in our `/src` directory and importing everything we will need: 261d9a481baSLucio Franco 262d9a481baSLucio Franco```rust 2634e5c6c8fSJuan Alvarezuse hello_world::greeter_client::GreeterClient; 2644e5c6c8fSJuan Alvarezuse hello_world::HelloRequest; 2654e5c6c8fSJuan Alvarez 266d9a481baSLucio Francopub mod hello_world { 267d9a481baSLucio Franco tonic::include_proto!("helloworld"); 268d9a481baSLucio Franco} 269d9a481baSLucio Franco``` 270d9a481baSLucio Franco 271d9a481baSLucio FrancoThe client is much simpler than the server as we don't need to implement any service methods, just make requests. Here is a Tokio runtime which will make our request and print the response to your terminal: 272d9a481baSLucio Franco 273d9a481baSLucio Franco```rust 274d9a481baSLucio Franco#[tokio::main] 275d9a481baSLucio Francoasync fn main() -> Result<(), Box<dyn std::error::Error>> { 276d9a481baSLucio Franco let mut client = GreeterClient::connect("http://[::1]:50051").await?; 277d9a481baSLucio Franco 278d9a481baSLucio Franco let request = tonic::Request::new(HelloRequest { 279d9a481baSLucio Franco name: "Tonic".into(), 280d9a481baSLucio Franco }); 281d9a481baSLucio Franco 282d9a481baSLucio Franco let response = client.say_hello(request).await?; 283d9a481baSLucio Franco 284d9a481baSLucio Franco println!("RESPONSE={:?}", response); 285d9a481baSLucio Franco 286d9a481baSLucio Franco Ok(()) 287d9a481baSLucio Franco} 288d9a481baSLucio Franco``` 289d9a481baSLucio Franco 290d9a481baSLucio FrancoThat's it! Our complete client file should look something like below, if it doesn't please go back and make sure you followed along correctly: 291d9a481baSLucio Franco 292d9a481baSLucio Franco```rust 2934e5c6c8fSJuan Alvarezuse hello_world::greeter_client::GreeterClient; 2944e5c6c8fSJuan Alvarezuse hello_world::HelloRequest; 2954e5c6c8fSJuan Alvarez 296d9a481baSLucio Francopub mod hello_world { 297d9a481baSLucio Franco tonic::include_proto!("helloworld"); 298d9a481baSLucio Franco} 299d9a481baSLucio Franco 300d9a481baSLucio Franco#[tokio::main] 301d9a481baSLucio Francoasync fn main() -> Result<(), Box<dyn std::error::Error>> { 302d9a481baSLucio Franco let mut client = GreeterClient::connect("http://[::1]:50051").await?; 303d9a481baSLucio Franco 304d9a481baSLucio Franco let request = tonic::Request::new(HelloRequest { 305d9a481baSLucio Franco name: "Tonic".into(), 306d9a481baSLucio Franco }); 307d9a481baSLucio Franco 308d9a481baSLucio Franco let response = client.say_hello(request).await?; 309d9a481baSLucio Franco 310d9a481baSLucio Franco println!("RESPONSE={:?}", response); 311d9a481baSLucio Franco 312d9a481baSLucio Franco Ok(()) 313d9a481baSLucio Franco} 314d9a481baSLucio Franco``` 315d9a481baSLucio Franco 316d9a481baSLucio Franco## Putting it all together 317d9a481baSLucio Franco 318d9a481baSLucio FrancoAt this point we have written our protobuf file, a build file to compile our protobufs, a server which implements our SayHello service, and a client which makes requests to our server. You should have a `proto/helloworld.proto` file, a `build.rs` file at the root of your project, and `src/server.rs` as well as a `src/client.rs` files. 319d9a481baSLucio Franco 320d9a481baSLucio FrancoTo run the server, run `cargo run --bin helloworld-server`. 321d9a481baSLucio FrancoTo run the client, run `cargo run --bin helloworld-client` in another terminal window. 322d9a481baSLucio Franco 323d9a481baSLucio FrancoYou should see the request logged out by the server in its terminal window, as well as the response logged out by the client in its window. 324d9a481baSLucio Franco 325d9a481baSLucio FrancoCongrats on making it through this introductory tutorial! We hope that this walkthrough tutorial has helped you understand the basics of Tonic, and how to get started writing high-performance, interoperable, and flexible gRPC servers in Rust. For a more in-depth tutorial which showcases an advanced gRPC server in Tonic, please see the [routeguide tutorial]. 326