diff --git a/examples/Cargo.toml b/examples/Cargo.toml index a91704248..660e8e2b2 100644 --- a/examples/Cargo.toml +++ b/examples/Cargo.toml @@ -186,6 +186,14 @@ path = "src/streaming/client.rs" name = "streaming-server" path = "src/streaming/server.rs" +[[bin]] +name = "json-codec-client" +path = "src/json-codec/client.rs" + +[[bin]] +name = "json-codec-server" +path = "src/json-codec/server.rs" + [dependencies] async-stream = "0.3" futures = { version = "0.3", default-features = false, features = ["alloc"] } diff --git a/examples/build.rs b/examples/build.rs index 8e4f64ff1..85b9f3473 100644 --- a/examples/build.rs +++ b/examples/build.rs @@ -1,5 +1,4 @@ -use std::env; -use std::path::PathBuf; +use std::{env, path::PathBuf}; fn main() { tonic_build::configure() @@ -30,4 +29,24 @@ fn main() { &["proto/googleapis"], ) .unwrap(); + + build_json_codec_service(); +} + +fn build_json_codec_service() { + let greeter_service = tonic_build::rust::Service::builder() + .name("Greeter") + .package("json.helloworld") + .method( + tonic_build::rust::Method::builder() + .name("say_hello") + .route_name("SayHello") + .input_type("crate::HelloRequest") + .output_type("crate::HelloResponse") + .codec_path("crate::JsonCodec") + .build(), + ) + .build(); + + tonic_build::rust::Builder::new().compile(&[greeter_service]); } diff --git a/examples/src/json-codec/client.rs b/examples/src/json-codec/client.rs new file mode 100644 index 000000000..109b71ed2 --- /dev/null +++ b/examples/src/json-codec/client.rs @@ -0,0 +1,23 @@ +use common::{HelloRequest, HelloResponse, JsonCodec}; +use hello_world::greeter_client::GreeterClient; + +pub mod common; + +pub mod hello_world { + include!(concat!(env!("OUT_DIR"), "/json.helloworld.Greeter.rs")); +} + +#[tokio::main] +async fn main() -> Result<(), Box> { + let mut client = GreeterClient::connect("http://[::1]:50051").await?; + + let request = tonic::Request::new(HelloRequest { + name: "Tonic".into(), + }); + + let response = client.say_hello(request).await?; + + println!("RESPONSE={:?}", response); + + Ok(()) +} diff --git a/examples/src/json-codec/common.rs b/examples/src/json-codec/common.rs new file mode 100644 index 000000000..4f17b7486 --- /dev/null +++ b/examples/src/json-codec/common.rs @@ -0,0 +1,76 @@ +use bytes::{Buf, BufMut}; +use serde::{Deserialize, Serialize}; +use std::marker::PhantomData; +use tonic::{ + codec::{Codec, DecodeBuf, Decoder, EncodeBuf, Encoder}, + Status, +}; + +#[derive(Debug, Deserialize, Serialize)] +pub struct HelloRequest { + pub name: String, +} + +#[derive(Debug, Deserialize, Serialize)] +pub struct HelloResponse { + pub message: String, +} + +#[derive(Debug)] +pub struct JsonEncoder(PhantomData); + +impl Encoder for JsonEncoder { + type Item = T; + type Error = Status; + + fn encode(&mut self, item: Self::Item, buf: &mut EncodeBuf<'_>) -> Result<(), Self::Error> { + serde_json::to_writer(buf.writer(), &item).map_err(|e| Status::internal(e.to_string())) + } +} + +#[derive(Debug)] +pub struct JsonDecoder(PhantomData); + +impl Decoder for JsonDecoder { + type Item = U; + type Error = Status; + + fn decode(&mut self, buf: &mut DecodeBuf<'_>) -> Result, Self::Error> { + if !buf.has_remaining() { + return Ok(None); + } + + let item: Self::Item = + serde_json::from_reader(buf.reader()).map_err(|e| Status::internal(e.to_string()))?; + Ok(Some(item)) + } +} + +/// A [`Codec`] that implements `application/grpc+bincode` via the serde library. +#[derive(Debug, Clone)] +pub struct JsonCodec(PhantomData<(T, U)>); + +impl Default for JsonCodec { + fn default() -> Self { + Self(PhantomData) + } +} + +impl Codec for JsonCodec +where + T: serde::Serialize + Send + 'static, + U: serde::de::DeserializeOwned + Send + 'static, +{ + type Encode = T; + type Decode = U; + type Encoder = JsonEncoder; + type Decoder = JsonDecoder; + + fn encoder(&mut self) -> Self::Encoder { + JsonEncoder(PhantomData) + } + + fn decoder(&mut self) -> Self::Decoder { + JsonDecoder(PhantomData) + } +} diff --git a/examples/src/json-codec/server.rs b/examples/src/json-codec/server.rs new file mode 100644 index 000000000..00e3fcadf --- /dev/null +++ b/examples/src/json-codec/server.rs @@ -0,0 +1,42 @@ +use tonic::{transport::Server, Request, Response, Status}; + +pub mod common; +use common::{HelloRequest, HelloResponse, JsonCodec}; + +pub mod hello_world { + include!(concat!(env!("OUT_DIR"), "/json.helloworld.Greeter.rs")); +} +use hello_world::greeter_server::{Greeter, GreeterServer}; + +#[derive(Default)] +pub struct MyGreeter {} + +#[tonic::async_trait] +impl Greeter for MyGreeter { + async fn say_hello( + &self, + request: Request, + ) -> Result, Status> { + println!("Got a request from {:?}", request.remote_addr()); + + let reply = HelloResponse { + message: format!("Hello {}!", request.into_inner().name), + }; + Ok(Response::new(reply)) + } +} + +#[tokio::main] +async fn main() -> Result<(), Box> { + let addr = "[::1]:50051".parse().unwrap(); + let greeter = MyGreeter::default(); + + println!("GreeterServer listening on {}", addr); + + Server::builder() + .add_service(GreeterServer::new(greeter)) + .serve(addr) + .await?; + + Ok(()) +}