xref: /tonic/examples/helloworld-tutorial.md (revision eb66b914)
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