xref: /tonic/examples/helloworld-tutorial.md (revision 5e9a5bcd)
1# Getting Started
2
3This 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!
4
5[rust]: https://www.rust-lang.org/
6[protocol buffers]: https://developers.google.com/protocol-buffers/docs/overview
7
8## Prerequisites
9
10To run the sample code and walk through the tutorial, the only prerequisite is Rust itself.
11[rustup] is a convenient tool to install it, if you haven't already.
12
13[rustup]: https://rustup.rs
14
15## Project Setup
16
17For this tutorial, we will start by creating a new Rust project with Cargo:
18
19```shell
20$ cargo new helloworld-tonic
21$ cd helloworld-tonic
22```
23
24`tonic` works on rust `1.39` and above as it requires support for the `async_await`
25feature.
26
27```bash
28$ rustup update
29```
30
31## Defining the HelloWorld service
32
33Our first step is to define the gRPC _service_ and the method _request_ and _response_ types using
34[protocol buffers]. We will keep our `.proto` files in a directory in our project's root.
35Note that Tonic does not really care where our `.proto` definitions live.
36
37```shell
38$ mkdir proto
39$ touch proto/helloworld.proto
40```
41
42Then you define RPC methods inside your service definition, specifying their request and response
43types. 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].
44
45[routeguide tutorial]: https://github.com/hyperium/tonic/blob/master/examples/routeguide-tutorial.md
46
47First 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`.
48
49```proto
50syntax = "proto3";
51package helloworld;
52```
53
54Next 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.
55
56```proto
57service Greeter {
58    // Our SayHello rpc accepts HelloRequests and returns HelloReplies
59    rpc SayHello (HelloRequest) returns (HelloReply);
60}
61```
62
63Finally, 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:
64
65```proto
66message HelloRequest {
67    // Request message contains the name to be greeted
68    string name = 1;
69}
70
71message HelloReply {
72    // Reply contains the greeting message
73    string message = 1;
74}
75```
76
77Great! Now our `.proto` file should be complete and ready for use in our application. Here is what it should look like completed:
78
79```proto
80syntax = "proto3";
81package helloworld;
82
83service Greeter {
84    rpc SayHello (HelloRequest) returns (HelloReply);
85}
86
87message HelloRequest {
88   string name = 1;
89}
90
91message HelloReply {
92    string message = 1;
93}
94```
95
96## Application Setup
97
98Now 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`.
99
100```toml
101[package]
102name = "helloworld-tonic"
103version = "0.1.0"
104edition = "2021"
105
106[[bin]] # Bin to run the HelloWorld gRPC server
107name = "helloworld-server"
108path = "src/server.rs"
109
110[[bin]] # Bin to run the HelloWorld gRPC client
111name = "helloworld-client"
112path = "src/client.rs"
113
114[dependencies]
115tonic = "*"
116prost = "0.13"
117tokio = { version = "1.0", features = ["macros", "rt-multi-thread"] }
118
119[build-dependencies]
120tonic-build = "*"
121```
122
123We 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:
124
125## Generating Server and Client code
126
127At the root of your project (not /src), create a `build.rs` file and add the following code:
128
129```rust
130fn main() -> Result<(), Box<dyn std::error::Error>> {
131    tonic_build::compile_protos("proto/helloworld.proto")?;
132    Ok(())
133}
134```
135
136This 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.
137
138[tonic-build]: https://github.com/hyperium/tonic/blob/master/tonic-build/README.md
139
140## Writing our Server
141
142Now 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:
143
144```rust
145use tonic::{transport::Server, Request, Response, Status};
146
147use hello_world::greeter_server::{Greeter, GreeterServer};
148use hello_world::{HelloReply, HelloRequest};
149
150pub mod hello_world {
151    tonic::include_proto!("helloworld"); // The string specified here must match the proto package name
152}
153```
154
155Next up, let's implement the Greeter service we previously defined in our `.proto` file. Here's what that might look like:
156
157```rust
158#[derive(Debug, Default)]
159pub struct MyGreeter {}
160
161#[tonic::async_trait]
162impl Greeter for MyGreeter {
163    async fn say_hello(
164        &self,
165        request: Request<HelloRequest>, // Accept request of type HelloRequest
166    ) -> Result<Response<HelloReply>, Status> { // Return an instance of type HelloReply
167        println!("Got a request: {:?}", request);
168
169        let reply = HelloReply {
170            message: format!("Hello {}!", request.into_inner().name), // We must use .into_inner() as the fields of gRPC requests and responses are private
171        };
172
173        Ok(Response::new(reply)) // Send back our formatted greeting
174    }
175}
176```
177
178Finally, 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!
179
180```rust
181#[tokio::main]
182async fn main() -> Result<(), Box<dyn std::error::Error>> {
183    let addr = "[::1]:50051".parse()?;
184    let greeter = MyGreeter::default();
185
186    Server::builder()
187        .add_service(GreeterServer::new(greeter))
188        .serve(addr)
189        .await?;
190
191    Ok(())
192}
193```
194
195Altogether your server should look something like this once you are done:
196
197```rust
198use tonic::{transport::Server, Request, Response, Status};
199
200use hello_world::greeter_server::{Greeter, GreeterServer};
201use hello_world::{HelloReply, HelloRequest};
202
203pub mod hello_world {
204    tonic::include_proto!("helloworld");
205}
206
207#[derive(Debug, Default)]
208pub struct MyGreeter {}
209
210#[tonic::async_trait]
211impl Greeter for MyGreeter {
212    async fn say_hello(
213        &self,
214        request: Request<HelloRequest>,
215    ) -> Result<Response<HelloReply>, Status> {
216        println!("Got a request: {:?}", request);
217
218        let reply = HelloReply {
219            message: format!("Hello {}!", request.into_inner().name),
220        };
221
222        Ok(Response::new(reply))
223    }
224}
225
226#[tokio::main]
227async fn main() -> Result<(), Box<dyn std::error::Error>> {
228    let addr = "[::1]:50051".parse()?;
229    let greeter = MyGreeter::default();
230
231    Server::builder()
232        .add_service(GreeterServer::new(greeter))
233        .serve(addr)
234        .await?;
235
236    Ok(())
237}
238```
239
240You 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.
241
242If you have a gRPC GUI client such as [Bloom RPC] you should be able to send requests to the server and get back greetings!
243
244Or if you use [grpcurl] then you can simply try send requests like this:
245```
246$ grpcurl -plaintext -import-path ./proto -proto helloworld.proto -d '{"name": "Tonic"}' '[::1]:50051' helloworld.Greeter/SayHello
247```
248And receiving responses like this:
249```
250{
251  "message": "Hello Tonic!"
252}
253```
254
255[bloom rpc]: https://github.com/uw-labs/bloomrpc
256[grpcurl]: https://github.com/fullstorydev/grpcurl
257
258## Writing our Client
259
260So 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:
261
262```rust
263use hello_world::greeter_client::GreeterClient;
264use hello_world::HelloRequest;
265
266pub mod hello_world {
267    tonic::include_proto!("helloworld");
268}
269```
270
271The 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:
272
273```rust
274#[tokio::main]
275async fn main() -> Result<(), Box<dyn std::error::Error>> {
276    let mut client = GreeterClient::connect("http://[::1]:50051").await?;
277
278    let request = tonic::Request::new(HelloRequest {
279        name: "Tonic".into(),
280    });
281
282    let response = client.say_hello(request).await?;
283
284    println!("RESPONSE={:?}", response);
285
286    Ok(())
287}
288```
289
290That'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:
291
292```rust
293use hello_world::greeter_client::GreeterClient;
294use hello_world::HelloRequest;
295
296pub mod hello_world {
297    tonic::include_proto!("helloworld");
298}
299
300#[tokio::main]
301async fn main() -> Result<(), Box<dyn std::error::Error>> {
302    let mut client = GreeterClient::connect("http://[::1]:50051").await?;
303
304    let request = tonic::Request::new(HelloRequest {
305        name: "Tonic".into(),
306    });
307
308    let response = client.say_hello(request).await?;
309
310    println!("RESPONSE={:?}", response);
311
312    Ok(())
313}
314```
315
316## Putting it all together
317
318At 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.
319
320To run the server, run `cargo run --bin helloworld-server`.
321To run the client, run `cargo run --bin helloworld-client` in another terminal window.
322
323You 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.
324
325Congrats 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