Skip to content

Commit

Permalink
feat(web): Add GrpcWebClientService (#1472)
Browse files Browse the repository at this point in the history
This adds `grpc-web` support for clients in `tonic-web`. This is done by
reusing the server side encoding/decoding but wrapping it in different
directions.
  • Loading branch information
LucioFranco authored Aug 25, 2023
1 parent 388b177 commit dc29c17
Show file tree
Hide file tree
Showing 5 changed files with 357 additions and 71 deletions.
2 changes: 1 addition & 1 deletion examples/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -270,7 +270,7 @@ routeguide = ["dep:async-stream", "tokio-stream", "dep:rand", "dep:serde", "dep:
reflection = ["dep:tonic-reflection"]
autoreload = ["tokio-stream/net", "dep:listenfd"]
health = ["dep:tonic-health"]
grpc-web = ["dep:tonic-web", "dep:bytes", "dep:http", "dep:hyper", "dep:tracing-subscriber"]
grpc-web = ["dep:tonic-web", "dep:bytes", "dep:http", "dep:hyper", "dep:tracing-subscriber", "dep:tower"]
tracing = ["dep:tracing", "dep:tracing-subscriber"]
hyper-warp = ["dep:either", "dep:tower", "dep:hyper", "dep:http", "dep:http-body", "dep:warp"]
hyper-warp-multiplex = ["hyper-warp"]
Expand Down
72 changes: 13 additions & 59 deletions examples/src/grpc-web/client.rs
Original file line number Diff line number Diff line change
@@ -1,74 +1,28 @@
use bytes::{Buf, BufMut, Bytes, BytesMut};
use hello_world::{HelloReply, HelloRequest};
use http::header::{ACCEPT, CONTENT_TYPE};
use hello_world::{greeter_client::GreeterClient, HelloRequest};
use tonic_web::GrpcWebClientLayer;

pub mod hello_world {
tonic::include_proto!("helloworld");
}

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let msg = HelloRequest {
name: "Bob".to_string(),
};
// Must use hyper directly...
let client = hyper::Client::builder().build_http();

// a good old http/1.1 request
let request = http::Request::builder()
.version(http::Version::HTTP_11)
.method(http::Method::POST)
.uri("http://127.0.0.1:3000/helloworld.Greeter/SayHello")
.header(CONTENT_TYPE, "application/grpc-web")
.header(ACCEPT, "application/grpc-web")
.body(hyper::Body::from(encode_body(msg)))
.unwrap();
let svc = tower::ServiceBuilder::new()
.layer(GrpcWebClientLayer::new())
.service(client);

let client = hyper::Client::new();
let mut client = GreeterClient::with_origin(svc, "http://127.0.0.1:3000".try_into()?);

let response = client.request(request).await.unwrap();
let request = tonic::Request::new(HelloRequest {
name: "Tonic".into(),
});

assert_eq!(
response.headers().get(CONTENT_TYPE).unwrap(),
"application/grpc-web+proto"
);
let response = client.say_hello(request).await?;

let body = response.into_body();
let reply = decode_body::<HelloReply>(body).await;

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

Ok(())
}

// one byte for the compression flag plus four bytes for the length
const GRPC_HEADER_SIZE: usize = 5;

fn encode_body<T>(msg: T) -> Bytes
where
T: prost::Message,
{
let msg_len = msg.encoded_len();
let mut buf = BytesMut::with_capacity(GRPC_HEADER_SIZE + msg_len);

// compression flag, 0 means "no compression"
buf.put_u8(0);
buf.put_u32(msg_len as u32);

msg.encode(&mut buf).unwrap();
buf.freeze()
}

async fn decode_body<T>(body: hyper::Body) -> T
where
T: Default + prost::Message,
{
let mut body = hyper::body::to_bytes(body).await.unwrap();

// ignore the compression flag
body.advance(1);

let len = body.get_u32();
#[allow(clippy::let_and_return)]
let msg = T::decode(&mut body.split_to(len as usize)).unwrap();

msg
}
Loading

0 comments on commit dc29c17

Please sign in to comment.