2020년에 Rust로 단일 바이너리 gRPC 서버-클라이언트를 구축해 봅시다 - 4부
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.
고객
우리는 홈 스트레치에 있습니다. 클라이언트를 구현합니다.
remotecli
라는 새 모듈을 client.rs
안에 만들 것입니다. 이 모듈은 서버에 대해 설정한 것과 동일한 패턴을 따릅니다.$ tree
.
├── build.rs
├── Cargo.toml
├── proto
│ └── cli.proto
└── src
├── main.rs
└── remotecli
├── client.rs
├── mod.rs
└── server.rs
remotecli/mod.rs
pub mod client;
pub mod server;
mod.rs
내에서 클라이언트 모듈을 선언하고 있습니다.remotecli/client.rs
우리 고객은 훨씬 더 간단합니다. 그러나 설명을 위해 모듈을 조각으로 나눕니다. 다시 말하지만 전체 파일은 섹션 끝에 있습니다.
수입품
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;
서버에서와 마찬가지로 모듈
remotecli_proto
을 만들고 tonic::include_proto!()
매크로를 사용하여 생성된 코드를 이 모듈에 복사/붙여넣기합니다.그런 다음 연결하기 위해 생성된
RemoteCliClient
와 CommandInput
구조체를 포함합니다. 이것이 우리가 서버로 보내는 것이기 때문입니다.마지막 포함은 프론트엔드의
RemoteCommandOptions
구조체이므로 연결하려는 서버 주소를 전달할 수 있습니다.client_run
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(())
}
도우미 함수
client_run()
는 우리 서버와 같은 async
함수입니다. 프론트엔드는 서버 주소 정보와 원시 사용자 명령에 대한 RemoteCommandOptions
구조체를 전달합니다.먼저
client
를 생성하고 RemoteCliClient::connect
로 서버에 연결하고 .await
를 수행합니다.그런 다음
tonic::Request
로 CommandInput
구조체를 생성하여 요청을 작성합니다.사용자 명령은 원시이며 서버가 기대하는 모양에 맞게 슬라이스해야 합니다. 사용자 명령의 첫 번째 단어는 쉘 명령이고 나머지는 인수입니다.
마지막으로
client
를 사용하고 요청과 함께 엔드포인트를 호출하고 .await
실행을 완료합니다.메인.rs
main.rs
의 최종 형태입니다. main.rs
에 마지막으로 하는 일은 client_run()
함수를 연결하는 것입니다.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 모두 함께
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(())
}
결론
우리는 사용자 입력을 구문 분석하고 gRPC를 사용하여 gRPC 클라이언트에서 서버로 명령을 전송하여 명령 출력을 실행하고 반환하는 CLI 애플리케이션을 구축하는 과정을 살펴보았습니다.
StructOpt
를 사용하여 프론트엔드 CLI를 구성한 방법에 따라 클라이언트와 서버 모두 단일 바이너리로 컴파일할 수 있습니다.프로토콜 버퍼(또는 protobuf)는 서버의 인터페이스와 사용된 데이터 구조를 정의하는 데 사용되었습니다.
Tonic
및 Prost
크레이트와 Cargo 빌드 스크립트는 protobuf를 네이티브 비동기 Rust 코드로 컴파일하는 데 사용되었습니다.Tokio
는 비동기 런타임이었습니다. async
/await
패턴을 지원하는 데 필요한 코드가 얼마나 적은지 경험했습니다.이 연습이 백엔드 코드에 gRPC를 사용하는 것에 대한 약간의 궁금증을 해소하기를 바랍니다. 뿐만 아니라 Rust 코드를 작성하는 데 관심이 생겼습니다.
Reference
이 문제에 관하여(2020년에 Rust로 단일 바이너리 gRPC 서버-클라이언트를 구축해 봅시다 - 4부), 우리는 이곳에서 더 많은 자료를 발견하고 링크를 클릭하여 보았다 https://dev.to/tjtelan/let-s-build-a-single-binary-grpc-server-client-with-rust-in-2020-part-4-3k9f텍스트를 자유롭게 공유하거나 복사할 수 있습니다.하지만 이 문서의 URL은 참조 URL로 남겨 두십시오.
우수한 개발자 콘텐츠 발견에 전념 (Collection and Share based on the CC Protocol.)