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