Checkpoint for gRPC implementation
This commit is contained in:
@@ -0,0 +1,29 @@
|
||||
[package]
|
||||
name = "hello-world"
|
||||
version = "0.1.0"
|
||||
edition = "2024"
|
||||
|
||||
[[bin]]
|
||||
name = "server"
|
||||
path = "src/bin/server.rs"
|
||||
|
||||
[[bin]]
|
||||
name = "client"
|
||||
path = "src/bin/client.rs"
|
||||
|
||||
[dependencies]
|
||||
roto-runtime = { path = "../../runtime" }
|
||||
roto-tonic = { path = "../../roto-tonic" }
|
||||
tonic = "0.12"
|
||||
tokio = { version = "1.38", features = ["full"] }
|
||||
tokio-stream = "0.1"
|
||||
bytes = "1.7"
|
||||
prost = "0.13"
|
||||
tower = "0.4"
|
||||
futures-util = "0.3"
|
||||
http-body-util = "0.1"
|
||||
http = "1.1"
|
||||
http-body = "1.0"
|
||||
|
||||
[build-dependencies]
|
||||
tonic-build = "0.12"
|
||||
@@ -0,0 +1,27 @@
|
||||
fn main() {
|
||||
let proto_file = "proto/hello.proto";
|
||||
let out_dir = std::env::var("OUT_DIR").unwrap();
|
||||
let dest_path = std::path::Path::new(&out_dir).join("hello.rs");
|
||||
|
||||
// Find the protoc-gen-roto binary
|
||||
// In a real scenario, this should be passed as an environment variable or found in PATH
|
||||
// For this example, we'll try to find it in the target directory
|
||||
let target_dir = std::env::current_dir().unwrap().join("../../target/debug");
|
||||
let plugin_path = target_dir.join("protoc-gen-roto");
|
||||
|
||||
if !plugin_path.exists() {
|
||||
panic!("protoc-gen-roto plugin not found at {:?}", plugin_path);
|
||||
}
|
||||
|
||||
let status = std::process::Command::new("protoc")
|
||||
.arg(format!("--plugin=protoc-gen-roto={}", plugin_path.display()))
|
||||
.arg(format!("--roto_out={}", out_dir))
|
||||
.arg(format!("--roto_opt=src=proto")) // Assuming the plugin handles this or we just pass it
|
||||
.arg(proto_file)
|
||||
.status()
|
||||
.expect("Failed to execute protoc");
|
||||
|
||||
if !status.success() {
|
||||
panic!("protoc failed with status {}", status);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
syntax = "proto3";
|
||||
|
||||
package hello;
|
||||
|
||||
service HelloWorldService {
|
||||
rpc HelloWorld (HelloRequest) returns (HelloResponse);
|
||||
}
|
||||
|
||||
message HelloRequest {
|
||||
string name = 1;
|
||||
}
|
||||
|
||||
message HelloResponse {
|
||||
string message = 1;
|
||||
}
|
||||
@@ -0,0 +1,210 @@
|
||||
// @generated by protoc-gen-roto — do not edit
|
||||
#[allow(unused_imports)]
|
||||
|
||||
use roto_runtime::{ProtoAccessor, ProtoBuilder, Result, RotoError, read_varint, RepeatedFieldIterator};
|
||||
use std::str;
|
||||
use bytes::Bytes;
|
||||
use tonic::{Request, Response, Status};
|
||||
use tokio_stream::Stream;
|
||||
use std::pin::Pin;
|
||||
|
||||
|
||||
pub struct HelloRequest<'a> {
|
||||
accessor: roto_runtime::ProtoAccessor<'a>,
|
||||
name_offset: Option<usize>,
|
||||
}
|
||||
|
||||
impl<'a> HelloRequest<'a> {
|
||||
pub fn new(data: &'a [u8]) -> roto_runtime::Result<Self> {
|
||||
let accessor = roto_runtime::ProtoAccessor::new(data)?;
|
||||
let mut name_offset = None;
|
||||
for item in accessor.fields() {
|
||||
let (offset, tag, _) = item?;
|
||||
if tag.field_number == 1 { name_offset = Some(offset); }
|
||||
}
|
||||
|
||||
Ok(Self {
|
||||
accessor,
|
||||
name_offset,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn name(&self) -> roto_runtime::Result<&'a str> {
|
||||
let offset = self.name_offset.ok_or(roto_runtime::RotoError::FieldNotFound)?;
|
||||
let (bytes, _) = self.accessor.get_value_at(offset)?;
|
||||
str::from_utf8(bytes).map_err(|_| roto_runtime::RotoError::WireFormatViolation)
|
||||
}
|
||||
|
||||
pub fn name_or_default(&self) -> roto_runtime::Result<&'a str> {
|
||||
self.name().or(Ok(""))
|
||||
}
|
||||
|
||||
pub fn has_name(&self) -> bool { self.name_offset.is_some() }
|
||||
|
||||
pub fn raw_fields(&self) -> roto_runtime::RawFieldIterator<'a> {
|
||||
self.accessor.raw_fields()
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
pub struct HelloRequestBuilder<'b> {
|
||||
builder: roto_runtime::ProtoBuilder<'b>,
|
||||
name_written: bool,
|
||||
}
|
||||
|
||||
impl<'b> HelloRequestBuilder<'b> {
|
||||
pub fn builder(buf: &mut [u8]) -> HelloRequestBuilder<'_> {
|
||||
HelloRequestBuilder {
|
||||
builder: roto_runtime::ProtoBuilder::new(buf),
|
||||
name_written: false,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn name(mut self, value: &str) -> roto_runtime::Result<Self> {
|
||||
self.builder.write_string(1, value)?;
|
||||
self.name_written = true;
|
||||
Ok(self)
|
||||
}
|
||||
|
||||
pub fn with(mut self, msg: &HelloRequest<'_>) -> roto_runtime::Result<Self> {
|
||||
for item in msg.raw_fields() {
|
||||
let (field_number, raw_bytes) = item?;
|
||||
let is_written = match field_number {
|
||||
1 => self.name_written,
|
||||
_ => false,
|
||||
};
|
||||
if !is_written {
|
||||
self.builder.write_raw(raw_bytes)?;
|
||||
}
|
||||
}
|
||||
Ok(self)
|
||||
}
|
||||
|
||||
pub fn finish(self) -> roto_runtime::Result<&'b mut [u8]> {
|
||||
self.builder.finish()
|
||||
}
|
||||
}
|
||||
|
||||
pub struct OwnedHelloRequest {
|
||||
pub data: bytes::Bytes,
|
||||
}
|
||||
|
||||
impl roto_runtime::RotoOwned for OwnedHelloRequest {
|
||||
type Reader<'a> = HelloRequest<'a>;
|
||||
fn reader(&self) -> HelloRequest<'_> {
|
||||
HelloRequest::new(&self.data).expect("failed to create reader")
|
||||
}
|
||||
}
|
||||
|
||||
impl roto_runtime::RotoMessage for OwnedHelloRequest {
|
||||
fn decode(buf: bytes::Bytes) -> roto_runtime::Result<Self> {
|
||||
Ok(OwnedHelloRequest { data: buf })
|
||||
}
|
||||
|
||||
fn bytes(&self) -> bytes::Bytes {
|
||||
self.data.clone()
|
||||
}
|
||||
}
|
||||
|
||||
pub struct HelloResponse<'a> {
|
||||
accessor: roto_runtime::ProtoAccessor<'a>,
|
||||
message_offset: Option<usize>,
|
||||
}
|
||||
|
||||
impl<'a> HelloResponse<'a> {
|
||||
pub fn new(data: &'a [u8]) -> roto_runtime::Result<Self> {
|
||||
let accessor = roto_runtime::ProtoAccessor::new(data)?;
|
||||
let mut message_offset = None;
|
||||
for item in accessor.fields() {
|
||||
let (offset, tag, _) = item?;
|
||||
if tag.field_number == 1 { message_offset = Some(offset); }
|
||||
}
|
||||
|
||||
Ok(Self {
|
||||
accessor,
|
||||
message_offset,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn message(&self) -> roto_runtime::Result<&'a str> {
|
||||
let offset = self.message_offset.ok_or(roto_runtime::RotoError::FieldNotFound)?;
|
||||
let (bytes, _) = self.accessor.get_value_at(offset)?;
|
||||
str::from_utf8(bytes).map_err(|_| roto_runtime::RotoError::WireFormatViolation)
|
||||
}
|
||||
|
||||
pub fn message_or_default(&self) -> roto_runtime::Result<&'a str> {
|
||||
self.message().or(Ok(""))
|
||||
}
|
||||
|
||||
pub fn has_message(&self) -> bool { self.message_offset.is_some() }
|
||||
|
||||
pub fn raw_fields(&self) -> roto_runtime::RawFieldIterator<'a> {
|
||||
self.accessor.raw_fields()
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
pub struct HelloResponseBuilder<'b> {
|
||||
builder: roto_runtime::ProtoBuilder<'b>,
|
||||
message_written: bool,
|
||||
}
|
||||
|
||||
impl<'b> HelloResponseBuilder<'b> {
|
||||
pub fn builder(buf: &mut [u8]) -> HelloResponseBuilder<'_> {
|
||||
HelloResponseBuilder {
|
||||
builder: roto_runtime::ProtoBuilder::new(buf),
|
||||
message_written: false,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn message(mut self, value: &str) -> roto_runtime::Result<Self> {
|
||||
self.builder.write_string(1, value)?;
|
||||
self.message_written = true;
|
||||
Ok(self)
|
||||
}
|
||||
|
||||
pub fn with(mut self, msg: &HelloResponse<'_>) -> roto_runtime::Result<Self> {
|
||||
for item in msg.raw_fields() {
|
||||
let (field_number, raw_bytes) = item?;
|
||||
let is_written = match field_number {
|
||||
1 => self.message_written,
|
||||
_ => false,
|
||||
};
|
||||
if !is_written {
|
||||
self.builder.write_raw(raw_bytes)?;
|
||||
}
|
||||
}
|
||||
Ok(self)
|
||||
}
|
||||
|
||||
pub fn finish(self) -> roto_runtime::Result<&'b mut [u8]> {
|
||||
self.builder.finish()
|
||||
}
|
||||
}
|
||||
|
||||
pub struct OwnedHelloResponse {
|
||||
pub data: bytes::Bytes,
|
||||
}
|
||||
|
||||
impl roto_runtime::RotoOwned for OwnedHelloResponse {
|
||||
type Reader<'a> = HelloResponse<'a>;
|
||||
fn reader(&self) -> HelloResponse<'_> {
|
||||
HelloResponse::new(&self.data).expect("failed to create reader")
|
||||
}
|
||||
}
|
||||
|
||||
impl roto_runtime::RotoMessage for OwnedHelloResponse {
|
||||
fn decode(buf: bytes::Bytes) -> roto_runtime::Result<Self> {
|
||||
Ok(OwnedHelloResponse { data: buf })
|
||||
}
|
||||
|
||||
fn bytes(&self) -> bytes::Bytes {
|
||||
self.data.clone()
|
||||
}
|
||||
}
|
||||
|
||||
#[tonic::async_trait]
|
||||
pub trait HelloWorldService: Send + Sync + 'static {
|
||||
async fn hello_world(&self, request: Request<OwnedHelloRequest>) -> std::result::Result<Response<OwnedHelloResponse>, Status>;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,64 @@
|
||||
use tonic::Request;
|
||||
use roto_tonic::RotoCodec;
|
||||
use hello::{HelloWorldService, OwnedHelloRequest, OwnedHelloResponse};
|
||||
use roto_runtime::RotoOwned;
|
||||
use std::task::{Context, Poll};
|
||||
use tower::Service;
|
||||
|
||||
pub mod hello {
|
||||
include!("../../proto/hello.rs");
|
||||
}
|
||||
|
||||
struct ReadyService<S>(S);
|
||||
|
||||
impl<S, Req> Service<Req> for ReadyService<S>
|
||||
where
|
||||
S: Service<Req>,
|
||||
{
|
||||
type Response = S::Response;
|
||||
type Error = S::Error;
|
||||
type Future = S::Future;
|
||||
|
||||
fn poll_ready(&mut self, _: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
|
||||
Poll::Ready(Ok(()))
|
||||
}
|
||||
|
||||
fn call(&mut self, req: Req) -> S::Future {
|
||||
self.0.call(req)
|
||||
}
|
||||
}
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
let channel = tonic::transport::Channel::from_static("http://[::1]:50051")
|
||||
.connect()
|
||||
.await?;
|
||||
|
||||
let ready_channel = ReadyService(channel);
|
||||
let mut client = tonic::client::Grpc::new(ready_channel);
|
||||
|
||||
// We need to specify the method path. For HelloWorldService/HelloWorld, it is "/hello.HelloWorldService/HelloWorld"
|
||||
let mut buf = vec![0u8; 1024];
|
||||
let slice = hello::HelloRequestBuilder::builder(&mut buf)
|
||||
.name("Roto").unwrap()
|
||||
.finish().unwrap();
|
||||
|
||||
let request = OwnedHelloRequest {
|
||||
data: bytes::Bytes::copy_from_slice(slice),
|
||||
};
|
||||
|
||||
// In tonic's Grpc client, we specify the codec separately.
|
||||
let response = client
|
||||
.unary(
|
||||
Request::new(request),
|
||||
http::uri::PathAndQuery::from_static("/hello.HelloWorldService/HelloWorld"),
|
||||
RotoCodec::<OwnedHelloResponse, OwnedHelloRequest>::default(),
|
||||
)
|
||||
.await?;
|
||||
|
||||
let response_msg: OwnedHelloResponse = response.into_inner();
|
||||
let reader = response_msg.reader();
|
||||
println!("Server responded: {}", reader.message().unwrap_or("No message"));
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@@ -0,0 +1,165 @@
|
||||
use std::pin::Pin;
|
||||
use std::future::Future;
|
||||
use std::task::{Context, Poll};
|
||||
use std::sync::Arc;
|
||||
use tonic::{transport::Server, Request, Response, Status};
|
||||
use roto_tonic::RotoCodec;
|
||||
use hello::{HelloWorldService, OwnedHelloRequest, OwnedHelloResponse};
|
||||
use tower::Service;
|
||||
use bytes::{Bytes, Buf, BufMut};
|
||||
use tonic::body::BoxBody;
|
||||
use futures_util::StreamExt;
|
||||
use roto_runtime::{RotoOwned, RotoMessage};
|
||||
use http_body_util::BodyExt;
|
||||
use http_body::Body;
|
||||
|
||||
pub mod hello {
|
||||
include!("../../proto/hello.rs");
|
||||
}
|
||||
|
||||
#[derive(Default, Clone)]
|
||||
pub struct MyHelloWorld {}
|
||||
|
||||
#[tonic::async_trait]
|
||||
impl HelloWorldService for MyHelloWorld {
|
||||
async fn hello_world(
|
||||
&self,
|
||||
request: Request<OwnedHelloRequest>,
|
||||
) -> Result<Response<OwnedHelloResponse>, Status> {
|
||||
let req = request.into_inner();
|
||||
let reader = req.reader();
|
||||
let name = reader.name().unwrap_or("Unknown");
|
||||
|
||||
let mut buf = vec![0u8; 1024];
|
||||
let slice = hello::HelloResponseBuilder::builder(&mut buf)
|
||||
.message(&format!("Hello {}!", name)).unwrap()
|
||||
.finish().unwrap();
|
||||
|
||||
let reply = OwnedHelloResponse {
|
||||
data: bytes::Bytes::copy_from_slice(slice),
|
||||
};
|
||||
|
||||
Ok(Response::new(reply))
|
||||
}
|
||||
}
|
||||
|
||||
// --- Tonic Glue ---
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct HelloWorldServer {
|
||||
inner: Arc<MyHelloWorld>,
|
||||
}
|
||||
|
||||
impl HelloWorldServer {
|
||||
pub fn new(inner: MyHelloWorld) -> Self {
|
||||
Self { inner: Arc::new(inner) }
|
||||
}
|
||||
}
|
||||
|
||||
impl tonic::server::NamedService for HelloWorldServer {
|
||||
const NAME: &'static str = "hello.HelloWorldService";
|
||||
}
|
||||
|
||||
struct StatusBody(Option<Bytes>);
|
||||
|
||||
impl Body for StatusBody {
|
||||
type Data = Bytes;
|
||||
type Error = Status;
|
||||
|
||||
fn poll_frame(
|
||||
mut self: Pin<&mut Self>,
|
||||
cx: &mut Context<'_>,
|
||||
) -> Poll<Option<Result<http_body::Frame<Self::Data>, Self::Error>>> {
|
||||
if let Some(data) = self.0.take() {
|
||||
Poll::Ready(Some(Ok(http_body::Frame::data(data))))
|
||||
} else {
|
||||
Poll::Ready(None)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Service<http::Request<BoxBody>> for HelloWorldServer {
|
||||
type Response = http::Response<BoxBody>;
|
||||
type Error = std::convert::Infallible;
|
||||
type Future = Pin<Box<dyn Future<Output = Result<Self::Response, Self::Error>> + Send>>;
|
||||
|
||||
fn poll_ready(&mut self, _: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
|
||||
Poll::Ready(Ok(()))
|
||||
}
|
||||
|
||||
fn call(&mut self, req: http::Request<BoxBody>) -> Self::Future {
|
||||
let inner = self.inner.clone();
|
||||
println!("Server received request: {} {}", req.method(), req.uri());
|
||||
|
||||
Box::pin(async move {
|
||||
let body = req.into_body();
|
||||
let bytes_vec = body.collect().await.map_err(|e| {
|
||||
println!("Body collect error: {}", e);
|
||||
panic!("Body collect error: {}", e);
|
||||
})?.to_bytes();
|
||||
println!("Collected body bytes: {} bytes", bytes_vec.len());
|
||||
|
||||
if bytes_vec.len() < 5 {
|
||||
println!("Body too short: {} bytes", bytes_vec.len());
|
||||
let res_body = BoxBody::new(StatusBody(Some(Bytes::from(vec![0, 0, 0, 0, 0]))));
|
||||
return Ok(http::Response::builder()
|
||||
.status(200)
|
||||
.body(res_body)
|
||||
.unwrap());
|
||||
}
|
||||
|
||||
let data = &bytes_vec[5..];
|
||||
println!("Decoding request from {} bytes", data.len());
|
||||
let request_msg = match OwnedHelloRequest::decode(Bytes::copy_from_slice(data)) {
|
||||
Ok(msg) => msg,
|
||||
Err(e) => {
|
||||
println!("Decode error: {}", e);
|
||||
let res_body = BoxBody::new(StatusBody(Some(Bytes::from(vec![0, 0, 0, 0, 0]))));
|
||||
return Ok(http::Response::builder().status(200).body(res_body).unwrap());
|
||||
}
|
||||
};
|
||||
|
||||
println!("Request decoded successfully");
|
||||
let response = match inner.hello_world(Request::new(request_msg)).await {
|
||||
Ok(res) => res,
|
||||
Err(e) => {
|
||||
println!("Service error: {}", e);
|
||||
let res_body = BoxBody::new(StatusBody(Some(Bytes::from(vec![0, 0, 0, 0, 0]))));
|
||||
return Ok(http::Response::builder().status(200).body(res_body).unwrap());
|
||||
}
|
||||
};
|
||||
|
||||
let response_msg = response.into_inner();
|
||||
let response_bytes = response_msg.bytes();
|
||||
println!("Service responded with {} bytes", response_bytes.len());
|
||||
|
||||
let mut res_buf = vec![0u8; 5 + response_bytes.len()];
|
||||
res_buf[0] = 0;
|
||||
let len = response_bytes.len() as u32;
|
||||
res_buf[1..5].copy_from_slice(&len.to_be_bytes());
|
||||
res_buf[5..].copy_from_slice(&response_bytes);
|
||||
|
||||
let res_body = BoxBody::new(StatusBody(Some(Bytes::from(res_buf))));
|
||||
Ok(http::Response::builder()
|
||||
.status(200)
|
||||
.header("content-type", "application/grpc")
|
||||
.body(res_body)
|
||||
.unwrap())
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
let addr: std::net::SocketAddr = "[::1]:50051".parse()?;
|
||||
let hello = MyHelloWorld::default();
|
||||
|
||||
println!("Server listening on {}", addr);
|
||||
|
||||
Server::builder()
|
||||
.add_service(HelloWorldServer::new(hello))
|
||||
.serve(addr)
|
||||
.await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
Reference in New Issue
Block a user