1 use bytes::{Buf, BufMut, Bytes, BytesMut}; 2 use hello_world::{HelloReply, HelloRequest}; 3 use http::header::{ACCEPT, CONTENT_TYPE}; 4 5 pub mod hello_world { 6 tonic::include_proto!("helloworld"); 7 } 8 9 #[tokio::main] 10 async fn main() -> Result<(), Box<dyn std::error::Error>> { 11 let msg = HelloRequest { 12 name: "Bob".to_string(), 13 }; 14 15 // a good old http/1.1 request 16 let request = http::Request::builder() 17 .version(http::Version::HTTP_11) 18 .method(http::Method::POST) 19 .uri("http://127.0.0.1:3000/helloworld.Greeter/SayHello") 20 .header(CONTENT_TYPE, "application/grpc-web") 21 .header(ACCEPT, "application/grpc-web") 22 .body(hyper::Body::from(encode_body(msg))) 23 .unwrap(); 24 25 let client = hyper::Client::new(); 26 27 let response = client.request(request).await.unwrap(); 28 29 assert_eq!( 30 response.headers().get(CONTENT_TYPE).unwrap(), 31 "application/grpc-web+proto" 32 ); 33 34 let body = response.into_body(); 35 let reply = decode_body::<HelloReply>(body).await; 36 37 println!("REPLY={:?}", reply); 38 39 Ok(()) 40 } 41 42 // one byte for the compression flag plus four bytes for the length 43 const GRPC_HEADER_SIZE: usize = 5; 44 45 fn encode_body<T>(msg: T) -> Bytes 46 where 47 T: prost::Message, 48 { 49 let msg_len = msg.encoded_len(); 50 let mut buf = BytesMut::with_capacity(GRPC_HEADER_SIZE + msg_len); 51 52 // compression flag, 0 means "no compression" 53 buf.put_u8(0); 54 buf.put_u32(msg_len as u32); 55 56 msg.encode(&mut buf).unwrap(); 57 buf.freeze() 58 } 59 60 async fn decode_body<T>(body: hyper::Body) -> T 61 where 62 T: Default + prost::Message, 63 { 64 let mut body = hyper::body::to_bytes(body).await.unwrap(); 65 66 // ignore the compression flag 67 body.advance(1); 68 69 let len = body.get_u32(); 70 #[allow(clippy::let_and_return)] 71 let msg = T::decode(&mut body.split_to(len as usize)).unwrap(); 72 73 msg 74 } 75