Let’s build a single binary gRPC server-client with Rust in 2020 - Part 4
Tags: rust, grpc, tutorial, showdev
Published: 2020-09-14 00:00:00

---
title: Let’s build a single binary gRPC server-client with Rust in 2020 - Part 4
published: true
date: 2020-09-14 00:00:00 UTC
tags: rust, grpc, tutorial, showdev
canonical_url: https://tjtelan.com/blog/lets-build-a-single-binary-grpc-server-client-with-rust-in-2020/#client
cover_image: https://dev-to-uploads.s3.amazonaws.com/i/v4vvjnbirspyrhh6usaf.png
series: Let’s build a single binary gRPC server-client with Rust in 2020
---

> In the previous post, we covered using our protobuf compiled Rust code to implement our gRPC server and include it in our CLI frontend.
>
> It is recommended that you follow in order since each post builds off the progress of the previous post.
>
> This is the last post of a 4 part series. If you would like to view this post in a single page, you can follow along [on my blog](https://tjtelan.com/blog/lets-build-a-single-binary-grpc-server-client-with-rust-in-2020).

---

## Client

We’re in the homestretch. Implementing a client. We’re going to create a new module within `remotecli` called `client.rs` that will follow the same patterns as we established for the server.

```bash
$ tree
.
├── build.rs
├── Cargo.toml
├── proto
│ └── cli.proto
└── src
	├── main.rs
	└── remotecli
    		├── client.rs
    		├── mod.rs
    		└── server.rs
```

### remotecli/mod.rs

```rust
pub mod client;
pub mod server;
```

We’re declaring the client module within `mod.rs`

### remotecli/client.rs

Our client is a lot more straightforward. But splitting the module up into pieces for description purposes. Again, full file is at the end of the secion

#### Imports

```rust
pub mod remotecli_proto {
   tonic::include_proto!("remotecli");
}

// Proto generated client
use remotecli_proto::remote_cli_client::RemoteCliClient;

// Proto message structs
use remotecli_proto::CommandInput;

use crate::RemoteCommandOptions;
```

Just like in our server, we create a module `remotecli_proto` and we use the `tonic::include_proto!()` macro to copy/paste our generated code into this module.

We then include the generated `RemoteCliClient` to connect, and the `CommandInput` struct since that is what we send over to the server.

Last include is the `RemoteCommandOptions` struct from the frontend so we can pass in the server address we want to connect to.

#### client\_run

```rust
pub async fn client_run(rc_opts: RemoteCommandOptions) -> Result<(), Box<dyn std::error::Error>> {
   // Connect to server
   // Use server addr if given, otherwise use default
   let mut client = RemoteCliClient::connect(rc_opts.server_addr).await?;

   let request = tonic::Request::new(CommandInput {
       command: rc_opts.command[0].clone().into(),
       args: rc_opts.command[1..].to_vec(),
   });

   let response = client.shell(request).await?;

   println!("RESPONSE={:?}", response);

   Ok(())
}
```

The helper function `client_run()` is an `async` function like our server. The frontend passes in a `RemoteCommandOptions` struct for the server address info as well as our raw user command.

* * *

First thing we do is create `client` and connect to the server with `RemoteCliClient::connect` and do an `.await`.

* * *

Then we build our request by creating a `tonic::Request` struct with our `CommandInput`.

The user command is raw and needs to be sliced up to fit the shape of what the server expects. The first word of the user command is the shell command, and the rest are the arguments.

* * *

Lastly we use `client` and call our endpoint with our request and `.await` for the execution to complete.

### main.rs

This is the final form of `main.rs`. The last thing we do to `main.rs` is plug in our `client_run()` function.

```rust
pub mod remotecli;

use structopt::StructOpt;

// These are the options used by the `server` subcommand
#[derive(Debug, StructOpt)]
pub struct ServerOptions {
   /// The address of the server that will run commands.
   #[structopt(long, default_value = "127.0.0.1:50051")]
   pub server_listen_addr: String,
}

// These are the options used by the `run` subcommand
#[derive(Debug, StructOpt)]
pub struct RemoteCommandOptions {
   /// The address of the server that will run commands.
   #[structopt(long = "server", default_value = "http://127.0.0.1:50051")]
   pub server_addr: String,
   /// The full command and arguments for the server to execute
   pub command: Vec<String>,
}

// These are the only valid values for our subcommands
#[derive(Debug, StructOpt)]
pub enum SubCommand {
   /// Start the remote command gRPC server
   #[structopt(name = "server")]
   StartServer(ServerOptions),
   /// Send a remote command to the gRPC server
   #[structopt(setting = structopt::clap::AppSettings::TrailingVarArg)]
   Run(RemoteCommandOptions),
}

// This is the main arguments structure that we'll parse from
#[derive(StructOpt, Debug)]
#[structopt(name = "remotecli")]
struct ApplicationArguments {
   #[structopt(flatten)]
   pub subcommand: SubCommand,
}

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
   let args = ApplicationArguments::from_args();

   match args.subcommand {
       SubCommand::StartServer(opts) => {
           println!("Start the server on: {:?}", opts.server_listen_addr);
           remotecli::server::start_server(opts).await?;
       }
       SubCommand::Run(rc_opts) => {
           println!("Run command: '{:?}'", rc_opts.command);
           remotecli::client::client_run(rc_opts).await?;
       }
   }

   Ok(())
}
```

### remotecli/client.rs all together

```rust
pub mod remotecli_proto {
   tonic::include_proto!("remotecli");
}

// Proto generated client
use remotecli_proto::remote_cli_client::RemoteCliClient;

// Proto message structs
use remotecli_proto::CommandInput;

use crate::RemoteCommandOptions;

pub async fn client_run(rc_opts: RemoteCommandOptions) -> Result<(), Box<dyn std::error::Error>> {
   // Connect to server
   // Use server addr if given, otherwise use default
   let mut client = RemoteCliClient::connect(rc_opts.server_addr).await?;

   let request = tonic::Request::new(CommandInput {
       command: rc_opts.command[0].clone().into(),
       args: rc_opts.command[1..].to_vec(),
   });

   let response = client.shell(request).await?;

   println!("RESPONSE={:?}", response);

   Ok(())
}
```

## Conclusion

We just walked through building a CLI application that parses user input and uses gRPC to send a command from a gRPC client to the server for execution and return of command output.

Based on how we structured the frontend CLI using `StructOpt`, we allowed both the client and server to compile into a single binary.

Protocol buffers (or protobufs) were used to define the interfaces of the server and the data structures that were used. The `Tonic` and `Prost` crates and Cargo build scripts were used to compile the protobufs into native async Rust code.

`Tokio` was our async runtime. We experienced how little code was necessary to support `async`/`await` patterns.

I hope that this walkthrough satisfies some curiosity about using gRPC for your backend code. As well as piqued your interest in writing some Rust code.