목차
- tonic을 이용해 grpc 통신을 구현해 보자
- Proto 파일 정의
- Server 모듈
- Client 모듈
- 실행 결과
tonic을 이용해 grpc 통신을 구현해 보자
tonic은 rust로 grpc를 구현한 오픈소스 라이브러리입니다. 즉, tonic을 이용해 grpc server와 client를 편하게 작성할 수 있습니다. Remote Procedure Call을 의미하는 rpc는 일반적인 HTTP 호출이 아닌 함수 호출의 개념을 사용하는 클라이언트-서버 통신의 한 형태입니다. grpc는 구글이 만든 오픈소스 rpc로 g는 Google을 의미하는 것으로 많이 알려져 있지만, 현재는 버전별 g의 의미를 다르게 나타내고 있습니다.
protobuf(Protocol Buffer)는 데이터 직렬화를 위한 포맷, 라이브러리 등을 제공하는 기술 정도로 이해할 수 있습니다. protobuf를 이해하기 위해서는 가장 먼저 proto 컴파일러에 의해 빌드되는 .proto
파일에 대해 알아야 합니다. 이 파일에는 프로토콜 버퍼 데이터를 정의하기 위한 구문을 작성하는데, .proto
파일이 컴파일되면 각 언어에 맞는 코드가 결과물로 생성됩니다. rust를 위한 protobuf 구현체인 prost가 이 역할을 하고 있고, tonic-build 모듈이 이 prost를 의존하고 있습니다.
이 포스팅에서는 rust, tonic을 이용해 grpc client와 server 프로그램의 간단한 예로 투표 시스템을 구현해 보겠습니다. 투표 시스템은 클라이언트가 서버에 UP 또는 DOWN을 투표하는 간단한 시스템입니다. 이 예제는 아래 References를 참고한 내용이며, .proto
파일 구문에 대한 내용 등 더 자세한 내용은 아래 References를 참고해 주세요.
Proto 파일 정의
먼저, voting.proto
파일에 주고받을 메시지 포맷을 정의합니다.
syntax = "proto3";
package voting;
service Voting {
rpc Vote (VotingRequest) returns (VotingResponse);
}
message VotingRequest {
string url = 1;
enum Vote {
UP = 0;
DOWN = 1;
}
Vote vote = 2;
}
message VotingResponse {
string confirmation = 1;
}
service 키워드를 통해 rpc 서비스를 정의하고, 그 내부에는 rpc 키워드를 통해 메서드들을 정의합니다. 여기서는 VotingRequest를 인자로 받아서 VotingResponse를 리턴하는 Vote 메서드를 갖고 있는 Voting 서비스를 정의했습니다
.
다음으로, message 키워드를 통해 VotingRequest, VotingResponse 메시지 포맷을 정의합니다. 메시지 타입을 정의할 때, 각 필드에는 유니크한 숫자가 할당됩니다. 현재, proto3 기준 enum 타입의 첫 번째 요소는 0부터 할당해야 합니다. VotingRequest는 string 타입의 url과 enum으로 정의한 Vote 타입의 vote, 이렇게 두 개의 필드를 갖고 있습니다. VotingResponse는 string 타입의 confirmation을 필드로 갖고 있습니다.
이제 이렇게 정의한 .proto 파일을 빌드해서 서버와 클라이언트 모듈에 포함시키고, 서버 모듈에서는 Voting 서비스의 vote 메서드를 구현할 것입니다.
Server 모듈 구현
서버 모듈에서는 .proto 파일에 정의한 Voting 서비스의 vote 메서드를 구현해야 합니다.
use tonic::{transport::Server, Request, Response, Status};
use voting::{VotingRequest, VotingResponse, voting_server::{Voting, VotingServer}};
pub mod voting {
tonic::include_proto!("voting");
}
#[derive(Debug, Default)]
pub struct VotingService {}
#[tonic::async_trait]
impl Voting for VotingService {
async fn vote(&self, request: Request<VotingRequest>) -> Result<Response<VotingResponse>, Status> {
let r = request.into_inner();
match r.vote {
0 => Ok(Response::new(voting::VotingResponse { confirmation: {
format!("Happy to confirm that you upvoted for {}", r.url)
}})),
1 => Ok(Response::new(voting::VotingResponse { confirmation : {
format!("Confirmation that you downvoted for {}", r.url)
}})),
_ => Err(Status::new(tonic::Code::OutOfRange, "Invalid vote provided")),
}
}
}
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let address = "[::1]:8080".parse().unwrap();
let voting_service = VotingService::default();
Server::builder().add_service(VotingServer::new(voting_service))
.serve(address)
.await?;
Ok(())
}
tonic::include_proto!("voting");
이 코드는 voting.proto
파일에 정의했던 패키지 명을 통해서 빌드된 결과물을 voting 모듈에 포함시키고 있습니다. 즉, proto 파일에 정의했던 Voting 서비스가 Voting trait 코드로 빌드되었고 이 trait을 voting 모듈에 포함시키는 것입니다.
그리고, VotingService는 이렇게 포함된 Voting trait을 구현합니다. vote 메서드의 구현 내용을 보면 VotingRequest 메시지가 담고 있는 vote 필드의 UP, DOWN에 따라 VotingResponse의 confirmation 메시지를 다르게 처리하는 것을 볼 수 있습니다.
Client 모듈 구현
클라이언트 모듈에서는 VotingRequest 메시지를 만들어 vote 메서드를 호출해야 합니다.
use std::io::stdin;
use voting::{VotingRequest, voting_client::VotingClient};
pub mod voting {
tonic::include_proto!("voting");
}
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let mut client = VotingClient::connect("http://[::1]:8080").await?;
loop {
println!("\nPlease vote for a paticular url");
let mut u = String::new();
let mut vote = String::new();
println!("Please provide a url: ");
stdin().read_line(&mut u).unwrap();
let u = u.trim();
println!("Please vote (d)own or (u)p: ");
stdin().read_line(&mut vote).unwrap();
let v = match vote.trim().to_lowercase().chars().next().unwrap() {
'u' => 0,
'd' => 1,
_ => break,
};
let request = tonic::Request::new(VotingRequest {
url: String::from(u),
vote: v,
});
let response = client.vote(request).await?;
println!("Got: '{}' from service", response.into_inner().confirmation);
}
Ok(())
}
클라이언트에서도 역시 voting.proto 파일의 결과물을 voting 모듈에 포함시킵니다. 그리고, 서버와 연결된 VotingClient 객체를 생성합니다. 이 객체는 vote 메서드를 갖고 있습니다. 이제, VotingRequest 타입의 메시지를 만들어서 vote 메서드를 호출하면 서버로 요청을 보내는 것입니다. 그 결과로 VotingResponse 탑의 메시지를 받게 되며 이를 출력하는 것을 볼 수 있습니다.
이제 서버 프로그램을 먼저 실행한 후 클라이언트 프로그램을 실행하면 아래와 같은 결과를 확인할 수 있습니다.
References
'Dev > Language' 카테고리의 다른 글
인자로 &String보다 &str을 사용하라 - Rust 프로그래밍 (0) | 2023.03.05 |
---|---|
impl Trait과 Box<dyn Trait> - Rust 프로그래밍 (1) | 2022.12.19 |
Cell 타입과 RefCell 타입 - Rust 프로그래밍 (1) | 2022.11.26 |
Rc 타입과 Weak 타입 - Rust 프로그래밍 (4) | 2022.10.29 |
소유권(Ownership)과 원시 타입 - Rust 프로그래밍 (0) | 2022.08.29 |