feat(examples): add grpc-web example (#710)

Creates a server using tonic-web and a client that uses a regular
`hyper::Client` and issues a regular HTTP/1.1 request.
This commit is contained in:
David Pedersen
2021-07-09 18:02:43 +02:00
committed by GitHub
parent 5bb1a029a1
commit 5aa8ae1fec
4 changed files with 176 additions and 35 deletions
+90
View File
@@ -0,0 +1,90 @@
use bytes::{Buf, BufMut, Bytes, BytesMut};
use hello_world::{HelloReply, HelloRequest};
use http::header::{ACCEPT, CONTENT_TYPE};
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(),
};
// 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 client = hyper::Client::new();
let response = client.request(request).await.unwrap();
assert_eq!(
response.headers().get(CONTENT_TYPE).unwrap(),
"application/grpc-web+proto"
);
let body = response.into_body();
let reply = decode_body::<HelloReply>(body).await;
println!("REPLY={:?}", reply);
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 mut buf = BytesMut::with_capacity(1024);
// first skip past the header
// cannot write it yet since we don't know the size of the
// encoded message
buf.reserve(GRPC_HEADER_SIZE);
unsafe {
buf.advance_mut(GRPC_HEADER_SIZE);
}
// write the message
msg.encode(&mut buf).unwrap();
// now we know the size of encoded message and can write the
// header
let len = buf.len() - GRPC_HEADER_SIZE;
{
let mut buf = &mut buf[..GRPC_HEADER_SIZE];
// compression flag, 0 means "no compression"
buf.put_u8(0);
buf.put_u32(len as u32);
}
buf.split_to(len + GRPC_HEADER_SIZE).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();
let msg = T::decode(&mut body.split_to(len as usize)).unwrap();
msg
}
+49
View File
@@ -0,0 +1,49 @@
use tonic::{transport::Server, Request, Response, Status};
use hello_world::greeter_server::{Greeter, GreeterServer};
use hello_world::{HelloReply, HelloRequest};
pub mod hello_world {
tonic::include_proto!("helloworld");
}
#[derive(Default)]
pub struct MyGreeter {}
#[tonic::async_trait]
impl Greeter for MyGreeter {
async fn say_hello(
&self,
request: Request<HelloRequest>,
) -> Result<Response<HelloReply>, Status> {
println!("Got a request from {:?}", request.remote_addr());
let reply = hello_world::HelloReply {
message: format!("Hello {}!", request.into_inner().name),
};
Ok(Response::new(reply))
}
}
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
tracing_subscriber::fmt::init();
let addr = "127.0.0.1:3000".parse().unwrap();
let greeter = MyGreeter::default();
let greeter = GreeterServer::new(greeter);
let greeter = tonic_web::config()
.allow_origins(vec!["127.0.0.1"])
.enable(greeter);
println!("GreeterServer listening on {}", addr);
Server::builder()
.accept_http1(true)
.add_service(greeter)
.serve(addr)
.await?;
Ok(())
}